Merge pull request #2351 from open-keychain/sql-delight

Use SqlDelight in favor of ContentProviders
This commit is contained in:
Vincent Breitmoser
2018-07-04 22:58:56 +02:00
committed by GitHub
333 changed files with 11216 additions and 17018 deletions

View File

@@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
apply plugin: 'jacoco'
apply plugin: 'com.squareup.sqldelight'
// apply plugin: 'com.github.kt3k.coveralls'
dependencies {
@@ -8,12 +9,12 @@ dependencies {
// NOTE: libraries are pinned to a specific build, see below
// from local Android SDK
compile 'com.android.support:support-v4:27.0.2'
compile 'com.android.support:appcompat-v7:27.0.2'
compile 'com.android.support:design:27.0.2'
compile 'com.android.support:recyclerview-v7:27.0.2'
compile 'com.android.support:cardview-v7:27.0.2'
compile 'com.android.support:support-annotations:27.0.2'
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'
// JCenter etc.
compile 'com.journeyapps:zxing-android-embedded:3.4.0'
@@ -27,15 +28,15 @@ dependencies {
// UI
compile 'org.sufficientlysecure:html-textview:3.1'
compile 'com.splitwise:tokenautocomplete:2.0.8@aar'
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'
// RecyclerView
compile 'com.tonicartos:superslim:0.4.13'
compile 'com.futuremind.recyclerfastscroll:fastscroll:0.2.4'
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'
// Material Drawer
compile 'com.mikepenz:materialdrawer:5.6.0@aar'
@@ -59,6 +60,9 @@ dependencies {
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"
// Unit tests in the local JVM with Robolectric
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
@@ -73,9 +77,9 @@ dependencies {
// 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.0.2'
androidTestCompile 'com.android.support:appcompat-v7:27.0.2'
androidTestCompile 'com.android.support:design:27.0.2'
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'
@@ -96,62 +100,69 @@ dependencies {
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"
// 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'
}
// Output of ./gradlew -q calculateChecksums
// Comment out the libs referenced as git submodules!
dependencyVerification {
verify = [
'com.android.support:design:fa5c27a705310e95a8f4099c98777132ed901a0d69178942306bb34cd76f0d57',
'eu.davidea:flexible-adapter-ui:7ed5327d15c823e5fcf7d6e1017d8a47d079d1adc7141858f3cb427517ef35cd',
'com.android.support:design:7225973f7ee03765008a9c2f17a40b154c6885169fef022276e811c926a2202c',
'com.journeyapps:zxing-android-embedded:2422d83c2c09a7b645f516c8458ececba6a7da47b94e40778d876facf495c660',
'org.sufficientlysecure:donations:2be4183afa5e35263e37346344cfea48681f3c987e6832dd4acde227c13ccad6',
'com.android.support:support-v4:1b2b37169fcccfef5e563d273749e3792decdce9818bc17932403a2363f537b4',
'com.futuremind.recyclerfastscroll:fastscroll:ae655201885a9dbb5fabecb4adfefbb23ffdbca26a2b4ea255ec1bf6f214c606',
'com.android.support:support-v4:4f41dfc3e89f2738e45c86264a85c0934d055ee8ebe2020e23c97f303b80a48b',
'com.mikepenz:fastadapter:21d4ecb5c128bcda37b14e7998d799ed52cfc768b72cdf3d5578bb6775769ebd',
'com.mikepenz:materialize:942ccf5e2aa1a46803aa884e8dc7bbaf2a9e8e9996a0cf92e3fe2f44a8592ba4',
'com.android.support:appcompat-v7:b2825e8b47f665d3362d8481c8d147d1af9230d16f23a2b94f6ccbc53c68cec1',
'com.android.support:appcompat-v7:0c7808fbbc5838d831e32e3c0a6f84e1f2c981deb8f11e010650f2b57923a335',
'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f',
'com.tonicartos:superslim:ca89b5c674660cc6918a8f8fd385065bffeee27983e0d33c7c2f0ad7b34d2d49',
'com.android.support:recyclerview-v7:3eb953930f10941f2b0447ec123a9b03d2746a42a99c523e82c3ece3308ca70b',
'com.android.support:cardview-v7:57f867a3c8f33e2d4dc0a03e2dfa03cad6267a908179f04a725a68ea9f0b8ccf',
'eu.davidea:flexible-adapter:560e940e8cf0f4ed8f632f5f89527deeda7a61cce5f02f42cc0983f7c0d2de5f',
'com.android.support:recyclerview-v7:d735e4727878e99ef3980c10d15dc3468462fd509d4fb60cb8bd20b0f735085c',
'com.android.support:cardview-v7:8ed955dd037d82a7b4bbcaedb4f896523c3e4c1bf3ca698ce807c350767a2886',
'org.sufficientlysecure:html-textview:ed740adf05cae2373999c7a3047c803183d9807b2cf66162902090d7c112a832',
'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240',
'com.android.support:transition:1a7db0453c1467fc8fd815e6d50ca6bb475a7a9ba6b5f3b307329688a7c62a68',
'com.android.support:support-media-compat:6dd9327ee9aa467cab479aad97df375072b2b6ba61eadffdaa5a88de3843c457',
'com.android.support:support-fragment:e4358388022a2205777575a7251fe357334658e4123d5d6e3b082f5899d9b011',
'com.android.support:support-core-utils:b69c6e1e7731b876b910fc7100bcadf40a57f27b32ca26b91400995542112c96',
'android.arch.persistence:db-framework:e8310c66979f8823cfe583951abfde96824b176289ba77b750a25be00d25981a',
'com.android.support:support-media-compat:55e9837dda88b74a8c812c63a78c63fd83c6c039a8c22d318492663a493585eb',
'com.jpardogo.materialtabstrip:library:4ee2f1211c302b45fb8c627cc5b240dc6b38b7aaaab1b8bffc81663e1b108013',
'com.android.support:animated-vector-drawable:5b117a2c13a898c2a3c84c480d64edcfac2ef720aa9b742c29249fac774ffc48',
'com.android.support:support-core-ui:2284072511a95d504c074de80c82cd33724c6d2754117833b98ba3a09994163e',
'com.android.support:support-vector-drawable:bf4f4fcbf58b1380616581224e6487c230bfdb3434ec353d4adaa4b1f4865cfa',
'com.android.support:support-compat:ed4d25d91a0b13d8b9def1c0de69ed03d7fb89d50fb37eb0e9b63b0cf7a42357',
'com.android.support:support-annotations:af05330d997eb92a066534dbe0a3ea24347d26d7001221092113ae02a8f233da',
'android.arch.lifecycle:extensions:851f718fd2afda1e7aa93537dae1a5c1fe47710db62dcd7cd24c4b3b14ef0d90',
'com.android.support:support-fragment:ec72d6ac36a1a0e6523bbddba33d73ffad070b9b3dd246cc44d8727a41ddb5e6',
'com.android.support:animated-vector-drawable:59670473f6e98fda792f7bef25dd7292b0a3106031c7a5e30eb020bf26f077bd',
'com.android.support:support-core-ui:a3ae20e6d5dffba69ac97b99846d2738003af8563843d5f3c9dc4c35b4804241',
'com.android.support:support-core-utils:61036832c54e8701aae954fc3bf96d1d80bf8d9dd531bff77d72def456ba087a',
'com.android.support:support-vector-drawable:1c0f421114cf4627cf208776d6eb4f76340c78b7e96fe6e12b3e6eb950caf1b9',
'com.android.support:transition:c0765b2f3c78696567ec5b3f519d22da1e3df11ac994625adf4bb4dc571caacc',
'com.android.support:support-compat:880ce01ff5be42b233ff8ec0c61cefb7dc3dc9500fea9e24423214813ac27ea2',
'android.arch.persistence:db:7c0a51d5fc890a8fb94a3370ff599243ec3485cca63daba3cc2bb197835dc521',
'android.arch.lifecycle:runtime:094fd793924dd6a5136753e599ac8174a8147f4a401386b694ba7d818c223e2e',
'android.arch.lifecycle:livedata-core:14e57ff8ffb65a80c7e72d91f2076acccdaf2970f234c6261e03a6127eb5206b',
'android.arch.lifecycle:common:614e31cfd33255dc4d5f5d8e62cfa6be2fbbc2a35643a79dc3ed008004c30807',
'android.arch.core:runtime:83400f7575bcfb8a2eeec64e05590f037bfaed1e56aa3a4214d20e55878445e3',
'android.arch.core:common:d34824b794bc92ff8f647a9bb13a7c73de920de5b47075b5d2c4f0770e9b8bfd',
'com.android.support:support-annotations:3365960206c3d2b09e845f555e7f88f8effc8d2f00b369e66c4be384029299cf',
'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d',
'org.commonjava.googlecode.markdown4j:markdown4j:28eb991f702c6d85d6cafd68c24d1ce841d1f5c995c943f25aedb433c0c13f60',
'com.squareup.okhttp3:okhttp-urlconnection:16a410e5c4457ab381759486df6f840fdc7cc426d67433d4da1b7d65ed2b3b33',
'com.squareup.okhttp3:okhttp:a0d01017a42bba26e507fc6d448bb36e536f4b6e612f7c42de30bbdac2b7785e',
'org.apache.james:apache-mime4j-dom:e18717fe6d36f32e5c5f7cbeea1a9bf04645fdabc84e7e8374d9da10fd52e78d',
'org.apache.james:apache-mime4j-core:561987f604911e1870b2b4eabf0b0658d666c66cb1e65fba3e9e4bffe63acab9',
'com.splitwise:tokenautocomplete:f921f83ee26b5265f719b312c30452ef8e219557826c5ce5bf02e29647967939',
'com.cocosw:bottomsheet:85bd91fd837b02ebd7a888501cb26035c7cd985a6aa87303fca249da8231a2c3',
'eu.davidea:flexible-adapter-livedata:c8718b46ff4fbf290ea18f0c5bfe8326badeadf5fd95899a1404c561a24f48a1',
'com.mikepenz:materialdrawer:8bba1428dcef5ad7c2decf49c612ad980b38e2f1031cbd66c152a8a104793929',
'com.mikepenz:iconics-core:478d7e245098f7c28b5b20a0e6b1e5cb108ef3eaf595af7190bc60f91063aa3d',
'com.mikepenz:google-material-typeface:f27c629ba5d2a90ecfbd7f221ff98cd363e1ee6be06b099b82bae490766e14a5',
'com.mikepenz:fontawesome-typeface:ee47b7fe97b90412f01f2fcdd78f65a4edb0ab00006f5ef59ed00516baca9309',
'com.mikepenz:community-material-typeface:d6035d261c5eba880cd7fe5dcb8cc00b09bfe6d41063b881b759e9897dc7b7c9',
'com.fidesmo:nordpol-android:9a992eca347ff7af6e99ff48078954b44b26f26fdc5463139e340234757a24f7',
'com.jakewharton.timber:timber:d553d3d3e883ce7d061f1b21b95d6ee0840f3bfbf6d3bd51c5671f0b0f0b0091',
'org.glassfish:javax.annotation:339c876b928766329cc0657920366e75beb25f932b80bb3b26df6c0e687a9582',
'com.ryanharter.auto.value:auto-value-parcel-adapter:f730534497f7de81f62f1165df65e750522fdaedabd56031ee1c2d9da2544e17',
'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
'com.fidesmo:nordpol-core:296e71b12884a9cd28cf00ab908973bbf776a90be1f23ac897380d91604e614d',
'com.jakewharton.timber:timber:d553d3d3e883ce7d061f1b21b95d6ee0840f3bfbf6d3bd51c5671f0b0f0b0091',
'android.arch.lifecycle:runtime:d0b36278878c82b838acc4308595bec61a3b5f6e7f2acc34172d7e071b2cf26d',
'android.arch.lifecycle:common:ff0215b54e7cbaaa898f8fd00e265ed6ea198859e10604bc1c5e78477df48b5c',
'android.arch.core:common:5192934cd73df32e2c15722ed7fc488dde90baaec9ae030010dd1a80fb4e74e1',
'android.arch.lifecycle:runtime:d0b36278878c82b838acc4308595bec61a3b5f6e7f2acc34172d7e071b2cf26d',
'android.arch.core:runtime:9e08fc5c4d6e48f58c6865b55ba0e72a88f907009407767274187a873e524734',
'android.arch.core:common:5192934cd73df32e2c15722ed7fc488dde90baaec9ae030010dd1a80fb4e74e1',
'android.arch.lifecycle:common:ff0215b54e7cbaaa898f8fd00e265ed6ea198859e10604bc1c5e78477df48b5c',
'android.arch.lifecycle:viewmodel:6407c93a5ea9850661dca42a0068d6f3deccefd7228ee69bae1c35d70cbc2557',
]
}

View File

@@ -38,8 +38,7 @@ 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.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
@@ -53,7 +52,7 @@ import static org.hamcrest.CoreMatchers.endsWith;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor;
public class TestHelpers {
public class AndroidTestHelpers {
public static void dismissSnackbar() {
onView(withClassName(endsWith("Snackbar")))
@@ -152,7 +151,7 @@ public class TestHelpers {
public static void cleanupForTests(Context context) throws Exception {
new KeychainDatabase(context).clearDatabase();
KeychainDatabase.getInstance(context).clearDatabase();
// import these two, make sure they're there
importKeysFromResource(context, "x.sec.asc");
@@ -161,5 +160,4 @@ public class TestHelpers {
PassphraseCacheService.clearCachedPassphrases(context);
}
}

View File

@@ -27,8 +27,7 @@ import android.view.View;
import com.tokenautocomplete.TokenCompleteTextView;
import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import static android.support.test.InstrumentationRegistry.getTargetContext;

View File

@@ -28,13 +28,9 @@ import android.widget.ViewAnimator;
import com.nispok.snackbar.Snackbar;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.ui.adapter.KeySectionedListAdapter;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
@@ -90,15 +86,14 @@ public abstract class CustomMatchers {
}
public static Matcher<RecyclerView.ViewHolder> withKeyHolderId(final long keyId) {
return new BoundedMatcher<RecyclerView.ViewHolder, KeySectionedListAdapter.KeyItemViewHolder>
(KeySectionedListAdapter.KeyItemViewHolder.class) {
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(KeySectionedListAdapter.KeyItemViewHolder item) {
protected boolean matchesSafely(View item) {
return item.getItemId() == keyId;
}
};

View File

@@ -9,7 +9,6 @@ import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ServiceTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import android.widget.AdapterView;
import org.junit.Before;
import org.junit.Rule;
@@ -23,17 +22,14 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import static android.support.test.espresso.Espresso.closeSoftKeyboard;
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.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.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.sufficientlysecure.keychain.TestHelpers.cleanupForTests;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.cleanupForTests;
@RunWith(AndroidJUnit4.class)

View File

@@ -32,7 +32,7 @@ import android.widget.AdapterView;
import org.junit.Before;
import org.junit.Rule;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.TestHelpers;
import org.sufficientlysecure.keychain.AndroidTestHelpers;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
@@ -61,11 +61,11 @@ 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.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.getImageNames;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.TestHelpers.pickRandom;
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
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;
@@ -95,7 +95,7 @@ public class AsymmetricFileOperationTests {
public void setUp() throws Exception {
Activity activity = mActivity.getActivity();
TestHelpers.copyFiles();
AndroidTestHelpers.copyFiles();
// import these two, make sure they're there
importKeysFromResource(activity, "x.sec.asc");

View File

@@ -43,9 +43,9 @@ 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.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
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;

View File

@@ -20,37 +20,27 @@ package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Intent;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.ViewActions;
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.rule.ActivityTestRule;
import android.support.v7.widget.RecyclerView;
import android.widget.AdapterView;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.runners.MethodSorters;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.matcher.CustomMatchers;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnHolderItem;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.allOf;
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyHolderId;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
//TODO This test is disabled because it needs to be fixed to work with updated code
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -73,7 +63,7 @@ public class EditKeyTest {
public void test01Edit() throws Exception {
Activity activity = mActivity.getActivity();
new KeychainDatabase(activity).clearDatabase();
KeychainDatabase.getInstance(activity).clearDatabase();
// import key for testing, get a stable initial state
importKeysFromResource(activity, "x.sec.asc");

View File

@@ -27,18 +27,15 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.espresso.intent.Intents;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.v7.widget.RecyclerView;
import android.widget.AdapterView;
import org.junit.Before;
import org.junit.Rule;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.TestHelpers;
import org.sufficientlysecure.keychain.matcher.CustomMatchers;
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;
@@ -46,7 +43,6 @@ import org.sufficientlysecure.keychain.util.Preferences;
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;
@@ -67,16 +63,15 @@ 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.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.dismissSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.getImageNames;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.TestHelpers.pickRandom;
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
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.CustomMatchers.withKeyItemId;
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
//TODO This test is disabled because it needs to be fixed to work with updated code
@@ -104,7 +99,7 @@ public class MiscCryptOperationTests {
mActivity = mActivityRule.getActivity();
TestHelpers.copyFiles();
AndroidTestHelpers.copyFiles();
// import these two, make sure they're there
importKeysFromResource(mActivity, "x.sec.asc");

View File

@@ -52,8 +52,8 @@ 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.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
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;

View File

@@ -1,120 +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.Before;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.runners.MethodSorters;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
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.intent.Intents.intending;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
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.assertThat;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.sufficientlysecure.keychain.TestHelpers.checkAndDismissSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.cleanupForTests;
//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 ViewKeyAdvShareTest {
@Rule
public final IntentsTestRule<ViewKeyAdvActivity> mActivityRule
= new IntentsTestRule<ViewKeyAdvActivity>(ViewKeyAdvActivity.class) {
@Override
protected Intent getActivityIntent() {
Intent intent = super.getActivityIntent();
intent.setData(KeyRings.buildGenericKeyRingUri(0x9D604D2F310716A3L));
intent.putExtra(ViewKeyAdvActivity.EXTRA_SELECTED_TAB, ViewKeyAdvActivity.TAB_SHARE);
return intent;
}
};
private Activity mActivity;
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
cleanupForTests(mActivity);
}
//@Test
public void testShareOperations() throws Exception {
// no-op should yield snackbar
onView(withId(R.id.view_key_action_fingerprint_clipboard)).perform(click());
checkAndDismissSnackbar(Style.OK, R.string.fingerprint_copied_to_clipboard);
assertThat("clipboard data is fingerprint", ClipboardReflection.getClipboardText(mActivity),
is("c619d53f7a5f96f391a84ca79d604d2f310716a3"));
intending(allOf(
hasAction("android.intent.action.CHOOSER"),
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
hasAction(Intent.ACTION_SEND),
hasType("text/plain"),
hasExtra(is(Intent.EXTRA_TEXT), is("openpgp4fpr:c619d53f7a5f96f391a84ca79d604d2f310716a3")),
hasExtra(is(Intent.EXTRA_STREAM),
allOf(hasScheme("content"), hasHost(TemporaryFileProvider.AUTHORITY)))
))
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
onView(withId(R.id.view_key_action_fingerprint_share)).perform(click());
onView(withId(R.id.view_key_action_key_clipboard)).perform(click());
checkAndDismissSnackbar(Style.OK, R.string.key_copied_to_clipboard);
assertThat("clipboard data is key",
ClipboardReflection.getClipboardText(mActivity), startsWith("----"));
intending(allOf(
hasAction("android.intent.action.CHOOSER"),
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
hasAction(Intent.ACTION_SEND),
hasType("text/plain"),
hasExtra(is(Intent.EXTRA_TEXT), startsWith("----")),
hasExtra(is(Intent.EXTRA_STREAM),
allOf(hasScheme("content"), hasHost(TemporaryFileProvider.AUTHORITY)))
))
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
onView(withId(R.id.view_key_action_key_share)).perform(click());
}
}

View File

@@ -40,7 +40,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.allOf;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.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;

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/ic_comment_text_grey600_24dp"
android:shortcutId="encrypt_text"
android:shortcutLongLabel="@string/btn_encrypt_text"
android:shortcutShortLabel="@string/btn_encrypt_text">
<intent
android:action="org.sufficientlysecure.keychain.action.ENCRYPT_FILES"
android:targetClass="org.sufficientlysecure.keychain.ui.EncryptTextActivity"
android:targetPackage="org.sufficientlysecure.keychain.debug" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/ic_folder_grey_24dp"
android:shortcutId="encrypt_files"
android:shortcutLongLabel="@string/btn_encrypt_files"
android:shortcutShortLabel="@string/btn_encrypt_files">
<intent
android:action="org.sufficientlysecure.keychain.action.ENCRYPT_TEXT"
android:targetClass="org.sufficientlysecure.keychain.ui.EncryptFilesActivity"
android:targetPackage="org.sufficientlysecure.keychain.debug" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/status_signature_unverified_cutout_24dp"
android:shortcutId="debug_actions"
android:shortcutLongLabel="@string/shortcut_debug"
android:shortcutShortLabel="@string/shortcut_debug">
<intent
android:action="org.sufficientlysecure.keychain.debug"
android:targetClass="org.sufficientlysecure.keychain.ui.DebugActionsActivity"
android:targetPackage="org.sufficientlysecure.keychain.debug" />
</shortcut>
</shortcuts>

View File

@@ -95,15 +95,6 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Keychain.Light">
<!-- broadcast receiver for Wi-Fi Connection -->
<receiver
android:name=".network.NetworkReceiver"
android:enabled="false"
android:exported="true" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<!-- broadcast receiver for Orbots status -->
<receiver android:name=".network.orbot.OrbotStatusReceiver">
<intent-filter>
@@ -128,6 +119,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
</activity>
<!-- singleTop for NFC dispatch, see SecurityTokenOperationActivity -->
<activity
@@ -177,15 +170,6 @@
<data android:mimeType="vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key" />
</intent-filter>
</activity>
<activity
android:name=".ui.ViewCertActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_view_cert"
android:parentActivityName=".ui.keyview.ViewKeyActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.keyview.ViewKeyActivity" />
</activity>
<activity
android:name=".ui.SafeSlingerActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@@ -983,6 +967,12 @@
android:name=".remote.ui.RemoteDisplayTransferCodeActivity"
android:theme="@style/Theme.Keychain.Transparent"/>
<activity android:name=".ui.DebugActionsActivity">
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.debug" />
</intent-filter>
</activity>
<!-- Usb interceptor activity -->
<activity
android:name=".ui.UsbEventReceiverActivity"
@@ -1070,21 +1060,6 @@
android:resource="@xml/sync_adapter_contacts_structure" />
</service>
<!-- keyserver sync service -->
<service
android:name=".service.KeyserverSyncAdapterService"
android:exported="true"
android:process=":sync"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter_keys" />
</service>
<!-- Storage Provider for temporary decrypted files.
For security considerations, read class! -->
<provider

View File

@@ -1,473 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v4.widget;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
/**
* Same as SwipeRefreshLayout, only updateContentOffsetTop and REFRESH_TRIGGER_DISTANCE
* have been modified!
*/
public class NoScrollableSwipeRefreshLayout extends ViewGroup {
private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300;
private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f;
private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
private static final float PROGRESS_BAR_HEIGHT = 4;
private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f;
private static final int REFRESH_TRIGGER_DISTANCE = 200;
private SwipeProgressBar mProgressBar; //the thing that shows progress is going
private View mTarget; //the content that gets pulled down
private int mOriginalOffsetTop;
private OnRefreshListener mListener;
private MotionEvent mDownEvent;
private int mFrom;
private boolean mRefreshing = false;
private int mTouchSlop;
private float mDistanceToTriggerSync = -1;
private float mPrevY;
private int mMediumAnimationDuration;
private float mFromPercentage = 0;
private float mCurrPercentage = 0;
private int mProgressBarHeight;
private int mCurrentTargetOffsetTop;
// Target is returning to its start offset because it was cancelled or a
// refresh was triggered.
private boolean mReturningToStart;
private final DecelerateInterpolator mDecelerateInterpolator;
private final AccelerateInterpolator mAccelerateInterpolator;
private static final int[] LAYOUT_ATTRS = new int[] {
android.R.attr.enabled
};
private final Animation mAnimateToStartPosition = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
int targetTop = 0;
if (mFrom != mOriginalOffsetTop) {
targetTop = (mFrom + (int)((mOriginalOffsetTop - mFrom) * interpolatedTime));
}
int offset = targetTop - mTarget.getTop();
final int currentTop = mTarget.getTop();
if (offset + currentTop < 0) {
offset = 0 - currentTop;
}
setTargetOffsetTopAndBottom(offset);
}
};
private Animation mShrinkTrigger = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
float percent = mFromPercentage + ((0 - mFromPercentage) * interpolatedTime);
mProgressBar.setTriggerPercentage(percent);
}
};
private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
// Once the target content has returned to its start position, reset
// the target offset to 0
mCurrentTargetOffsetTop = 0;
}
};
private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
mCurrPercentage = 0;
}
};
private final Runnable mReturnToStartPosition = new Runnable() {
@Override
public void run() {
mReturningToStart = true;
animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),
mReturnToStartPositionListener);
}
};
// Cancel the refresh gesture and animate everything back to its original state.
private final Runnable mCancel = new Runnable() {
@Override
public void run() {
mReturningToStart = true;
// Timeout fired since the user last moved their finger; animate the
// trigger to 0 and put the target back at its original position
if (mProgressBar != null) {
mFromPercentage = mCurrPercentage;
mShrinkTrigger.setDuration(mMediumAnimationDuration);
mShrinkTrigger.setAnimationListener(mShrinkAnimationListener);
mShrinkTrigger.reset();
mShrinkTrigger.setInterpolator(mDecelerateInterpolator);
startAnimation(mShrinkTrigger);
}
animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),
mReturnToStartPositionListener);
}
};
/**
* Simple constructor to use when creating a SwipeRefreshLayout from code.
* @param context
*/
public NoScrollableSwipeRefreshLayout(Context context) {
this(context, null);
}
/**
* Constructor that is called when inflating SwipeRefreshLayout from XML.
* @param context
* @param attrs
*/
public NoScrollableSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMediumAnimationDuration = getResources().getInteger(
android.R.integer.config_mediumAnimTime);
setWillNotDraw(false);
mProgressBar = new SwipeProgressBar(this);
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mProgressBarHeight = (int) (metrics.density * PROGRESS_BAR_HEIGHT);
mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
mAccelerateInterpolator = new AccelerateInterpolator(ACCELERATE_INTERPOLATION_FACTOR);
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
setEnabled(a.getBoolean(0, true));
a.recycle();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
removeCallbacks(mCancel);
removeCallbacks(mReturnToStartPosition);
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallbacks(mReturnToStartPosition);
removeCallbacks(mCancel);
}
private void animateOffsetToStartPosition(int from, AnimationListener listener) {
mFrom = from;
mAnimateToStartPosition.reset();
mAnimateToStartPosition.setDuration(mMediumAnimationDuration);
mAnimateToStartPosition.setAnimationListener(listener);
mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
mTarget.startAnimation(mAnimateToStartPosition);
}
/**
* Set the listener to be notified when a refresh is triggered via the swipe
* gesture.
*/
public void setOnRefreshListener(OnRefreshListener listener) {
mListener = listener;
}
private void setTriggerPercentage(float percent) {
if (percent == 0f) {
// No-op. A null trigger means it's uninitialized, and setting it to zero-percent
// means we're trying to reset state, so there's nothing to reset in this case.
mCurrPercentage = 0;
return;
}
mCurrPercentage = percent;
mProgressBar.setTriggerPercentage(percent);
}
/**
* Notify the widget that refresh state has changed. Do not call this when
* refresh is triggered by a swipe gesture.
*
* @param refreshing Whether or not the view should show refresh progress.
*/
public void setRefreshing(boolean refreshing) {
if (mRefreshing != refreshing) {
ensureTarget();
mCurrPercentage = 0;
mRefreshing = refreshing;
if (mRefreshing) {
mProgressBar.start();
} else {
mProgressBar.stop();
}
}
}
/**
* Set the four colors used in the progress animation. The first color will
* also be the color of the bar that grows in response to a user swipe
* gesture.
*
* @param colorRes1 Color resource.
* @param colorRes2 Color resource.
* @param colorRes3 Color resource.
* @param colorRes4 Color resource.
*/
public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
ensureTarget();
final Resources res = getResources();
final int color1 = res.getColor(colorRes1);
final int color2 = res.getColor(colorRes2);
final int color3 = res.getColor(colorRes3);
final int color4 = res.getColor(colorRes4);
mProgressBar.setColorScheme(color1, color2, color3,color4);
}
/**
* @return Whether the SwipeRefreshWidget is actively showing refresh
* progress.
*/
public boolean isRefreshing() {
return mRefreshing;
}
private void ensureTarget() {
// Don't bother getting the parent height if the parent hasn't been laid out yet.
if (mTarget == null) {
if (getChildCount() > 1 && !isInEditMode()) {
throw new IllegalStateException(
"SwipeRefreshLayout can host only one direct child");
}
mTarget = getChildAt(0);
mOriginalOffsetTop = mTarget.getTop() + getPaddingTop();
}
if (mDistanceToTriggerSync == -1) {
if (getParent() != null && ((View)getParent()).getHeight() > 0) {
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mDistanceToTriggerSync = (int) Math.min(
((View) getParent()) .getHeight() * MAX_SWIPE_DISTANCE_FACTOR,
REFRESH_TRIGGER_DISTANCE * metrics.density);
}
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
mProgressBar.draw(canvas);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int width = getMeasuredWidth();
final int height = getMeasuredHeight();
mProgressBar.setBounds(0, 0, width, mProgressBarHeight);
if (getChildCount() == 0) {
return;
}
final View child = getChildAt(0);
final int childLeft = getPaddingLeft();
final int childTop = mCurrentTargetOffsetTop + getPaddingTop();
final int childWidth = width - getPaddingLeft() - getPaddingRight();
final int childHeight = height - getPaddingTop() - getPaddingBottom();
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 1 && !isInEditMode()) {
throw new IllegalStateException("SwipeRefreshLayout can host only one direct child");
}
if (getChildCount() > 0) {
getChildAt(0).measure(
MeasureSpec.makeMeasureSpec(
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(
getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
MeasureSpec.EXACTLY));
}
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
boolean handled = false;
if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
if (isEnabled() && !mReturningToStart && !canChildScrollUp()) {
handled = onTouchEvent(ev);
}
return !handled ? super.onInterceptTouchEvent(ev) : handled;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// Nope.
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
boolean handled = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
mCurrPercentage = 0;
mDownEvent = MotionEvent.obtain(event);
mPrevY = mDownEvent.getY();
break;
case MotionEvent.ACTION_MOVE:
if (mDownEvent != null && !mReturningToStart) {
final float eventY = event.getY();
float yDiff = eventY - mDownEvent.getY();
if (yDiff > mTouchSlop) {
// User velocity passed min velocity; trigger a refresh
if (yDiff > mDistanceToTriggerSync) {
// User movement passed distance; trigger a refresh
startRefresh();
handled = true;
break;
} else {
// Just track the user's movement
setTriggerPercentage(
mAccelerateInterpolator.getInterpolation(
yDiff / mDistanceToTriggerSync));
float offsetTop = yDiff;
if (mPrevY > eventY) {
offsetTop = yDiff - mTouchSlop;
}
updateContentOffsetTop((int) (offsetTop));
if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) {
// If the user puts the view back at the top, we
// don't need to. This shouldn't be considered
// cancelling the gesture as the user can restart from the top.
removeCallbacks(mCancel);
} else {
updatePositionTimeout();
}
mPrevY = event.getY();
handled = true;
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mDownEvent != null) {
mDownEvent.recycle();
mDownEvent = null;
}
break;
}
return handled;
}
private void startRefresh() {
removeCallbacks(mCancel);
mReturnToStartPosition.run();
setRefreshing(true);
mListener.onRefresh();
}
private void updateContentOffsetTop(int targetTop) {
final int currentTop = mTarget.getTop();
if (targetTop > mDistanceToTriggerSync) {
targetTop = (int) mDistanceToTriggerSync;
} else if (targetTop < 0) {
targetTop = 0;
}
// setTargetOffsetTopAndBottom(targetTop - currentTop);
}
private void setTargetOffsetTopAndBottom(int offset) {
mTarget.offsetTopAndBottom(offset);
mCurrentTargetOffsetTop = mTarget.getTop();
}
private void updatePositionTimeout() {
removeCallbacks(mCancel);
postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT);
}
/**
* Classes that wish to be notified when the swipe gesture correctly
* triggers a refresh should implement this interface.
*/
public interface OnRefreshListener {
void onRefresh();
}
/**
* Simple AnimationListener to avoid having to implement unneeded methods in
* AnimationListeners.
*/
private class BaseAnimationListener implements AnimationListener {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
}

View File

@@ -37,6 +37,8 @@ public final class Constants {
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
public static final boolean DEBUG_KEYSERVER_SYNC = false;
public static final boolean IS_RUNNING_UNITTEST = isRunningUnitTest();
public static final String TAG = DEBUG ? "Keychain D" : "Keychain";
public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain";
@@ -108,9 +110,14 @@ public final class Constants {
public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain");
}
public static final class Notification {
public static final class NotificationIds {
public static final int PASSPHRASE_CACHE = 1;
public static final int KEYSERVER_SYNC_FAIL_ORBOT = 2;
public static final int KEYSERVER_SYNC = 3;
}
public static final class NotificationChannels {
public static final String KEYSERVER_SYNC = "keyserverSync";
}
public static final class Pref {
@@ -145,6 +152,7 @@ public final class Constants {
public static final String SYNC_CONTACTS = "syncContacts";
public static final String SYNC_KEYSERVER = "syncKeyserver";
public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly";
public static final String SYNC_IS_SCHEDULED = "syncIsScheduled";
// other settings
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";
public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase";
@@ -205,4 +213,12 @@ public final class Constants {
public static final KeyFormat SECURITY_TOKEN_V2_DEC = new RSAKeyFormat(2048, ELEN, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
public static final KeyFormat SECURITY_TOKEN_V2_AUTH = new RSAKeyFormat(2048, ELEN, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS);
private static boolean isRunningUnitTest() {
try {
Class.forName("org.sufficientlysecure.keychain.KeychainTestRunner");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@@ -24,13 +24,8 @@ import java.util.HashMap;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Application;
import android.app.job.JobScheduler;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.widget.Toast;
@@ -38,8 +33,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.network.TlsCertificatePinning;
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
import org.sufficientlysecure.keychain.util.PRNGFixes;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
@@ -95,26 +89,22 @@ public class KeychainApplication extends Application {
if (preferences.isAppExecutedFirstTime()) {
preferences.setAppExecutedFirstTime(false);
KeyserverSyncAdapterService.enableKeyserverSync(this);
ContactSyncAdapterService.enableContactsSync(this);
preferences.setPrefVersionToCurrentVersion();
}
if (Preferences.getKeyserverSyncEnabled(this)) {
// will update a keyserver sync if the interval has changed
KeyserverSyncAdapterService.updateInterval(this);
}
// Upgrade preferences as needed
preferences.upgradePreferences(this);
preferences.upgradePreferences();
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(), "DigiCertGlobalRootCA.cer");
new Handler().postDelayed(() -> TemporaryFileProvider.cleanUp(getApplicationContext()), 1000);
KeyserverSyncManager.updateKeyserverSyncSchedule(this, Constants.DEBUG_KEYSERVER_SYNC);
TemporaryFileProvider.scheduleCleanupImmediately();
}
/**

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.provider;
package org.sufficientlysecure.keychain;
import java.io.File;
@@ -23,23 +23,20 @@ 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 android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import android.support.annotation.VisibleForTesting;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns;
import org.sufficientlysecure.keychain.daos.LocalSecretKeyStorage;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignaturesColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings;
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
@@ -53,185 +50,86 @@ import timber.log.Timber;
* - TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).
* - BLOB. The value is a blob of data, stored exactly as it was input.
*/
public class KeychainDatabase extends SQLiteOpenHelper {
public class KeychainDatabase {
private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 25;
private Context mContext;
private static final int DATABASE_VERSION = 29;
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
private static KeychainDatabase sInstance;
public static KeychainDatabase getInstance(Context context) {
if (sInstance == null || Constants.IS_RUNNING_UNITTEST) {
sInstance = new KeychainDatabase(context.getApplicationContext());
}
return sInstance;
}
public interface Tables {
String KEY_RINGS_PUBLIC = "keyrings_public";
String KEY_RINGS_SECRET = "keyrings_secret";
String KEYS = "keys";
String UPDATED_KEYS = "updated_keys";
String KEY_SIGNATURES = "key_signatures";
String USER_PACKETS = "user_packets";
String CERTS = "certs";
String API_APPS = "api_apps";
String API_ALLOWED_KEYS = "api_allowed_keys";
String OVERRIDDEN_WARNINGS = "overridden_warnings";
String API_AUTOCRYPT_PEERS = "api_autocrypt_peers";
}
private static final String CREATE_KEYRINGS_PUBLIC =
"CREATE TABLE IF NOT EXISTS keyrings_public ("
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ KeyRingsColumns.KEY_RING_DATA + " BLOB"
+ ")";
private KeychainDatabase(Context context) {
supportSQLiteOpenHelper =
new FrameworkSQLiteOpenHelperFactory()
.create(Configuration.builder(context).name(DATABASE_NAME).callback(
new Callback(DATABASE_VERSION) {
@Override
public void onCreate(SupportSQLiteDatabase db) {
KeychainDatabase.this.onCreate(db, context);
}
private static final String CREATE_KEYRINGS_SECRET =
"CREATE TABLE IF NOT EXISTS keyrings_secret ("
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ KeyRingsColumns.KEY_RING_DATA + " BLOB, "
+ "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") "
+ "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
@Override
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
KeychainDatabase.this.onUpgrade(db, context, oldVersion, newVersion);
}
private static final String CREATE_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
+ KeysColumns.MASTER_KEY_ID + " INTEGER, "
+ KeysColumns.RANK + " INTEGER, "
@Override
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
KeychainDatabase.this.onDowngrade();
}
+ KeysColumns.KEY_ID + " INTEGER, "
+ KeysColumns.KEY_SIZE + " INTEGER, "
+ KeysColumns.KEY_CURVE_OID + " TEXT, "
+ KeysColumns.ALGORITHM + " INTEGER, "
+ KeysColumns.FINGERPRINT + " BLOB, "
+ KeysColumns.CAN_CERTIFY + " INTEGER, "
+ KeysColumns.CAN_SIGN + " INTEGER, "
+ KeysColumns.CAN_ENCRYPT + " INTEGER, "
+ KeysColumns.CAN_AUTHENTICATE + " INTEGER, "
+ KeysColumns.IS_REVOKED + " INTEGER, "
+ KeysColumns.HAS_SECRET + " INTEGER, "
+ KeysColumns.IS_SECURE + " INTEGER, "
+ KeysColumns.CREATION + " INTEGER, "
+ KeysColumns.EXPIRY + " INTEGER, "
+ "PRIMARY KEY(" + KeysColumns.MASTER_KEY_ID + ", " + KeysColumns.RANK + "),"
+ "FOREIGN KEY(" + KeysColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_USER_PACKETS =
"CREATE TABLE IF NOT EXISTS " + Tables.USER_PACKETS + "("
+ UserPacketsColumns.MASTER_KEY_ID + " INTEGER, "
+ UserPacketsColumns.TYPE + " INT, "
+ UserPacketsColumns.USER_ID + " TEXT, "
+ UserPacketsColumns.NAME + " TEXT, "
+ UserPacketsColumns.EMAIL + " TEXT, "
+ UserPacketsColumns.COMMENT + " TEXT, "
+ UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, "
+ UserPacketsColumns.IS_PRIMARY + " INTEGER, "
+ UserPacketsColumns.IS_REVOKED + " INTEGER, "
+ UserPacketsColumns.RANK+ " INTEGER, "
+ "PRIMARY KEY(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + "), "
+ "FOREIGN KEY(" + UserPacketsColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_CERTS =
"CREATE TABLE IF NOT EXISTS " + Tables.CERTS + "("
+ CertsColumns.MASTER_KEY_ID + " INTEGER,"
+ CertsColumns.RANK + " INTEGER, " // rank of certified uid
+ CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key
+ CertsColumns.TYPE + " INTEGER, "
+ CertsColumns.VERIFIED + " INTEGER, "
+ CertsColumns.CREATION + " INTEGER, "
+ CertsColumns.DATA + " BLOB, "
+ "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", "
+ CertsColumns.KEY_ID_CERTIFIER + "), "
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE,"
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES "
+ Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_UPDATE_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " ("
+ UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, "
+ UpdatedKeysColumns.LAST_UPDATED + " INTEGER, "
+ UpdatedKeysColumns.SEEN_ON_KEYSERVERS + " INTEGER, "
+ "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_KEY_SIGNATURES =
"CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " ("
+ KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, "
+ KeySignaturesColumns.SIGNER_KEY_ID + " INTEGER NOT NULL, "
+ "PRIMARY KEY(" + KeySignaturesColumns.MASTER_KEY_ID + ", " + KeySignaturesColumns.SIGNER_KEY_ID + "), "
+ "FOREIGN KEY(" + KeySignaturesColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_API_AUTOCRYPT_PEERS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_AUTOCRYPT_PEERS + " ("
+ ApiAutocryptPeerColumns.PACKAGE_NAME + " TEXT NOT NULL, "
+ ApiAutocryptPeerColumns.IDENTIFIER + " TEXT NOT NULL, "
+ ApiAutocryptPeerColumns.LAST_SEEN + " INTEGER, "
+ ApiAutocryptPeerColumns.LAST_SEEN_KEY + " INTEGER NULL, "
+ ApiAutocryptPeerColumns.IS_MUTUAL + " INTEGER NULL, "
+ ApiAutocryptPeerColumns.MASTER_KEY_ID + " INTEGER NULL, "
+ ApiAutocryptPeerColumns.GOSSIP_MASTER_KEY_ID + " INTEGER NULL, "
+ ApiAutocryptPeerColumns.GOSSIP_LAST_SEEN_KEY + " INTEGER NULL, "
+ ApiAutocryptPeerColumns.GOSSIP_ORIGIN + " INTEGER NULL, "
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
+ ApiAutocryptPeerColumns.IDENTIFIER + "), "
+ "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES "
+ Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_API_APPS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
+ ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB"
+ ")";
private static final String CREATE_API_APPS_ALLOWED_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsAllowedKeysColumns.KEY_ID + " INTEGER, "
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + " TEXT NOT NULL, "
+ "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", "
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), "
+ "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES "
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_OVERRIDDEN_WARNINGS =
"CREATE TABLE IF NOT EXISTS " + Tables.OVERRIDDEN_WARNINGS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ OverriddenWarnings.IDENTIFIER + " TEXT NOT NULL UNIQUE "
+ ")";
public KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
@Override
public void onOpen(SupportSQLiteDatabase db) {
super.onOpen(db);
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
if (Constants.DEBUG) {
recreateUnifiedKeyView(db);
}
}
}
}).build());
}
@Override
public void onCreate(SQLiteDatabase db) {
public SupportSQLiteDatabase getReadableDatabase() {
return supportSQLiteOpenHelper.getReadableDatabase();
}
public SupportSQLiteDatabase getWritableDatabase() {
return supportSQLiteOpenHelper.getWritableDatabase();
}
private void onCreate(SupportSQLiteDatabase db, Context context) {
Timber.w("Creating database...");
db.execSQL(CREATE_KEYRINGS_PUBLIC);
db.execSQL(CREATE_KEYRINGS_SECRET);
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS);
db.execSQL(CREATE_UPDATE_KEYS);
db.execSQL(CREATE_KEY_SIGNATURES);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
db.execSQL(CREATE_OVERRIDDEN_WARNINGS);
db.execSQL(CREATE_API_AUTOCRYPT_PEERS);
db.execSQL(KeyRingsPublicModel.CREATE_TABLE);
db.execSQL(KeysModel.CREATE_TABLE);
db.execSQL(UserPacketsModel.CREATE_TABLE);
db.execSQL(CertsModel.CREATE_TABLE);
db.execSQL(KeyMetadataModel.CREATE_TABLE);
db.execSQL(KeySignaturesModel.CREATE_TABLE);
db.execSQL(ApiAppsModel.CREATE_TABLE);
db.execSQL(OverriddenWarningsModel.CREATE_TABLE);
db.execSQL(AutocryptPeersModel.CREATE_TABLE);
db.execSQL(ApiAllowedKeysModel.CREATE_TABLE);
db.execSQL(KeysModel.UNIFIEDKEYVIEW);
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");");
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
@@ -241,20 +139,10 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("CREATE INDEX uids_by_email ON user_packets ("
+ UserPacketsColumns.EMAIL + ");");
Preferences.getPreferences(mContext).setKeySignaturesTableInitialized();
Preferences.getPreferences(context).setKeySignaturesTableInitialized();
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
private void onUpgrade(SupportSQLiteDatabase db, Context context, int oldVersion, int newVersion) {
Timber.d("Upgrading db from " + oldVersion + " to " + newVersion);
switch (oldVersion) {
@@ -293,7 +181,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
case 7:
// new table for allowed key ids in API
try {
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
db.execSQL(ApiAppsModel.CREATE_TABLE);
} catch (Exception e) {
// never mind, the column probably already existed
}
@@ -302,37 +190,37 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("DROP TABLE IF EXISTS certs");
db.execSQL("DROP TABLE IF EXISTS user_ids");
db.execSQL("CREATE TABLE IF NOT EXISTS user_packets("
+ "master_key_id INTEGER, "
+ "type INT, "
+ "user_id TEXT, "
+ "attribute_data BLOB, "
+ "master_key_id INTEGER, "
+ "type INT, "
+ "user_id TEXT, "
+ "attribute_data BLOB, "
+ "is_primary INTEGER, "
+ "is_revoked INTEGER, "
+ "rank INTEGER, "
+ "is_primary INTEGER, "
+ "is_revoked INTEGER, "
+ "rank INTEGER, "
+ "PRIMARY KEY(master_key_id, rank), "
+ "FOREIGN KEY(master_key_id) REFERENCES "
+ "PRIMARY KEY(master_key_id, rank), "
+ "FOREIGN KEY(master_key_id) REFERENCES "
+ "keyrings_public(master_key_id) ON DELETE CASCADE"
+ ")");
+ ")");
db.execSQL("CREATE TABLE IF NOT EXISTS certs("
+ "master_key_id INTEGER,"
+ "rank INTEGER, " // rank of certified uid
+ "master_key_id INTEGER,"
+ "rank INTEGER, " // rank of certified uid
+ "key_id_certifier INTEGER, " // certifying key
+ "type INTEGER, "
+ "verified INTEGER, "
+ "creation INTEGER, "
+ "key_id_certifier INTEGER, " // certifying key
+ "type INTEGER, "
+ "verified INTEGER, "
+ "creation INTEGER, "
+ "data BLOB, "
+ "data BLOB, "
+ "PRIMARY KEY(master_key_id, rank, "
+ "PRIMARY KEY(master_key_id, rank, "
+ "key_id_certifier), "
+ "FOREIGN KEY(master_key_id) REFERENCES "
+ "FOREIGN KEY(master_key_id) REFERENCES "
+ "keyrings_public(master_key_id) ON DELETE CASCADE,"
+ "FOREIGN KEY(master_key_id, rank) REFERENCES "
+ "FOREIGN KEY(master_key_id, rank) REFERENCES "
+ "user_packets(master_key_id, rank) ON DELETE CASCADE"
+ ")");
+ ")");
case 9:
// do nothing here, just consolidate
case 10:
@@ -371,20 +259,12 @@ public class KeychainDatabase extends SQLiteOpenHelper {
case 19:
// emergency fix for crashing consolidate
db.execSQL("UPDATE keys SET is_secure = 1;");
/* TODO actually drop this table. leaving it around for now!
case 20:
db.execSQL("DROP TABLE api_accounts");
if (oldVersion == 20) {
// no need to consolidate
return;
}
*/
case 20:
db.execSQL(
"CREATE TABLE IF NOT EXISTS overridden_warnings ("
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "identifier TEXT NOT NULL UNIQUE "
+ ")");
db.execSQL(
"CREATE TABLE IF NOT EXISTS overridden_warnings ("
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "identifier TEXT NOT NULL UNIQUE "
+ ")");
case 21:
try {
@@ -400,10 +280,10 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "last_updated INTEGER NOT NULL, "
+ "last_seen_key INTEGER NOT NULL, "
+ "state INTEGER NOT NULL, "
+ "master_key_id INTEGER NULL, "
+ "master_key_id INTEGER, "
+ "PRIMARY KEY(package_name, identifier), "
+ "FOREIGN KEY(package_name) REFERENCES api_apps(package_name) ON DELETE CASCADE"
+ ")");
+ ")");
case 23:
db.execSQL("CREATE TABLE IF NOT EXISTS key_signatures ("
@@ -413,7 +293,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE"
+ ")");
case 24:
case 24: {
try {
db.beginTransaction();
db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO tmp");
@@ -456,11 +336,74 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("CREATE INDEX IF NOT EXISTS uids_by_email ON user_packets (email);");
db.execSQL("DROP INDEX keys_by_rank");
db.execSQL("CREATE INDEX keys_by_rank ON keys(rank, master_key_id);");
}
case 25: {
try {
migrateSecretKeysFromDbToLocalStorage(db, context);
} catch (IOException e) {
throw new IllegalStateException("Error migrating secret keys! This is bad!!");
}
}
case 26:
migrateUpdatedKeysToKeyMetadataTable(db);
case 27:
renameApiAutocryptPeersTable(db);
case 28:
recreateUnifiedKeyView(db);
// drop old table from version 20
db.execSQL("DROP TABLE IF EXISTS api_accounts");
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
private void recreateUnifiedKeyView(SupportSQLiteDatabase db) {
// noinspection deprecation
db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME);
db.execSQL(KeysModel.UNIFIEDKEYVIEW);
}
private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db, Context context) throws IOException {
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret");
while (cursor.moveToNext()) {
long masterKeyId = cursor.getLong(0);
byte[] secretKeyBlob = cursor.getBlob(1);
localSecretKeyStorage.writeSecretKey(masterKeyId, secretKeyBlob);
}
cursor.close();
// we'll keep this around for now, but make sure to delete when migration looks ok!!
// db.execSQL("DROP TABLE keyrings_secret");
}
private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) {
try {
db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;");
} catch (SQLException e) {
if (Constants.DEBUG) {
Timber.e(e, "Ignoring migration exception, this probably happened before");
return;
}
throw e;
}
}
private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) {
try {
db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;");
} catch (SQLException e) {
if (Constants.DEBUG) {
Timber.e(e, "Ignoring migration exception, this probably happened before");
return;
}
throw e;
}
}
private void onDowngrade() {
// Downgrade is ok for the debug version, makes it easier to work with branches
if (Constants.DEBUG) {
return;
@@ -512,9 +455,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// DANGEROUS, use in test code ONLY!
public void clearDatabase() {
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS);
getWritableDatabase().execSQL("delete from " + Tables.API_APPS);
getWritableDatabase().execSQL("delete from " + KeyRingsPublicModel.TABLE_NAME);
getWritableDatabase().execSQL("delete from " + ApiAllowedKeysModel.TABLE_NAME);
getWritableDatabase().execSQL("delete from api_apps");
}
}

View File

@@ -0,0 +1,67 @@
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 android.database.Cursor;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
class AbstractDao {
private final KeychainDatabase db;
private final DatabaseNotifyManager databaseNotifyManager;
AbstractDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) {
this.db = db;
this.databaseNotifyManager = databaseNotifyManager;
}
SupportSQLiteDatabase getReadableDb() {
return db.getReadableDatabase();
}
SupportSQLiteDatabase getWritableDb() {
return db.getWritableDatabase();
}
DatabaseNotifyManager getDatabaseNotifyManager() {
return databaseNotifyManager;
}
<T> List<T> mapAllRows(SupportSQLiteQuery query, Mapper<T> mapper) {
ArrayList<T> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
T item = mapper.map(cursor);
result.add(item);
}
}
return result;
}
<T> T mapSingleRowOrThrow(SupportSQLiteQuery query, Mapper<T> mapper) throws NotFoundException {
T result = mapSingleRow(query, mapper);
if (result == null) {
throw new NotFoundException();
}
return result;
}
<T> T mapSingleRow(SupportSQLiteQuery query, Mapper<T> mapper) {
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToNext()) {
return mapper.map(cursor);
}
}
return null;
}
interface Mapper<T> {
T map(Cursor cursor);
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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.daos;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.Context;
import android.database.Cursor;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.ApiAllowedKeysModel.InsertAllowedKey;
import org.sufficientlysecure.keychain.ApiAppsModel;
import org.sufficientlysecure.keychain.ApiAppsModel.DeleteByPackageName;
import org.sufficientlysecure.keychain.ApiAppsModel.InsertApiApp;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.model.ApiAllowedKey;
import org.sufficientlysecure.keychain.model.ApiApp;
public class ApiAppDao extends AbstractDao {
public static ApiAppDao getInstance(Context context) {
KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
return new ApiAppDao(keychainDatabase, databaseNotifyManager);
}
private ApiAppDao(KeychainDatabase keychainDatabase, DatabaseNotifyManager databaseNotifyManager) {
super(keychainDatabase, databaseNotifyManager);
}
public ApiApp getApiApp(String packageName) {
try (Cursor cursor = getReadableDb().query(ApiApp.FACTORY.selectByPackageName(packageName))) {
if (cursor.moveToFirst()) {
return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor);
}
return null;
}
}
public byte[] getApiAppCertificate(String packageName) {
try (Cursor cursor = getReadableDb().query(ApiApp.FACTORY.getCertificate(packageName))) {
if (cursor.moveToFirst()) {
return ApiApp.FACTORY.getCertificateMapper().map(cursor);
}
return null;
}
}
public void insertApiApp(ApiApp apiApp) {
ApiApp existingApiApp = getApiApp(apiApp.package_name());
if (existingApiApp != null) {
if (!Arrays.equals(existingApiApp.package_signature(), apiApp.package_signature())) {
throw new IllegalStateException("Inserting existing api with different signature?!");
}
return;
}
InsertApiApp statement = new ApiAppsModel.InsertApiApp(getWritableDb());
statement.bind(apiApp.package_name(), apiApp.package_signature());
statement.executeInsert();
getDatabaseNotifyManager().notifyApiAppChange(apiApp.package_name());
}
public void deleteApiApp(String packageName) {
DeleteByPackageName deleteByPackageName = new DeleteByPackageName(getWritableDb());
deleteByPackageName.bind(packageName);
deleteByPackageName.executeUpdateDelete();
getDatabaseNotifyManager().notifyApiAppChange(packageName);
}
public HashSet<Long> getAllowedKeyIdsForApp(String packageName) {
SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName);
HashSet<Long> keyIds = new HashSet<>();
try (Cursor cursor = getReadableDb().query(allowedKeys)) {
while (cursor.moveToNext()) {
long allowedKeyId = ApiAllowedKey.FACTORY.getAllowedKeysMapper().map(cursor);
keyIds.add(allowedKeyId);
}
}
return keyIds;
}
public void saveAllowedKeyIdsForApp(String packageName, Set<Long> allowedKeyIds) {
ApiAllowedKey.DeleteByPackageName deleteByPackageName = new ApiAllowedKey.DeleteByPackageName(getWritableDb());
deleteByPackageName.bind(packageName);
deleteByPackageName.executeUpdateDelete();
InsertAllowedKey statement = new InsertAllowedKey(getWritableDb());
for (Long keyId : allowedKeyIds) {
statement.bind(packageName, keyId);
statement.execute();
}
getDatabaseNotifyManager().notifyApiAppChange(packageName);
}
public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) {
InsertAllowedKey statement = new InsertAllowedKey(getWritableDb());
statement.bind(packageName, allowedKeyId);
statement.execute();
getDatabaseNotifyManager().notifyApiAppChange(packageName);
}
public List<ApiApp> getAllApiApps() {
SqlDelightQuery query = ApiApp.FACTORY.selectAll();
ArrayList<ApiApp> result = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor);
result.add(apiApp);
}
}
return result;
}
}

View File

@@ -0,0 +1,160 @@
/*
* 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.daos;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.support.annotation.Nullable;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByIdentifier;
import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByMasterKeyId;
import org.sufficientlysecure.keychain.AutocryptPeersModel.InsertPeer;
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateGossipKey;
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateKey;
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateLastSeen;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.model.AutocryptPeer;
import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus;
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
public class AutocryptPeerDao extends AbstractDao {
public static AutocryptPeerDao getInstance(Context context) {
KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
return new AutocryptPeerDao(keychainDatabase, databaseNotifyManager);
}
private AutocryptPeerDao(KeychainDatabase database, DatabaseNotifyManager databaseNotifyManager) {
super(database, databaseNotifyManager);
}
public Long getMasterKeyIdForAutocryptPeer(String autocryptId) {
SqlDelightQuery query = AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifier(autocryptId);
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToFirst()) {
return AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifierMapper().map(cursor);
}
}
return null;
}
@Nullable
public AutocryptPeer getAutocryptPeer(String packageName, String autocryptId) {
List<AutocryptPeer> autocryptPeers = getAutocryptPeers(packageName, autocryptId);
if (!autocryptPeers.isEmpty()) {
return autocryptPeers.get(0);
}
return null;
}
private List<AutocryptPeer> getAutocryptPeers(String packageName, String... autocryptId) {
ArrayList<AutocryptPeer> result = new ArrayList<>(autocryptId.length);
SqlDelightQuery query = AutocryptPeer.FACTORY.selectByIdentifiers(packageName, autocryptId);
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToNext()) {
AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor);
result.add(autocryptPeer);
}
}
return result;
}
public List<AutocryptKeyStatus> getAutocryptKeyStatus(String packageName, String[] autocryptIds) {
ArrayList<AutocryptKeyStatus> result = new ArrayList<>(autocryptIds.length);
SqlDelightQuery query = AutocryptPeer.FACTORY.selectAutocryptKeyStatus(packageName, autocryptIds, System.currentTimeMillis());
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToNext()) {
AutocryptKeyStatus autocryptPeer = AutocryptPeer.KEY_STATUS_MAPPER.map(cursor);
result.add(autocryptPeer);
}
}
return result;
}
private void ensureAutocryptPeerExists(String packageName, String autocryptId) {
InsertPeer insertStatement = new InsertPeer(getWritableDb());
insertStatement.bind(packageName, autocryptId);
insertStatement.executeInsert();
}
public void insertOrUpdateLastSeen(String packageName, String autocryptId, Date date) {
ensureAutocryptPeerExists(packageName, autocryptId);
UpdateLastSeen updateStatement = new UpdateLastSeen(getWritableDb(), AutocryptPeer.FACTORY);
updateStatement.bind(packageName, autocryptId, date);
updateStatement.executeUpdateDelete();
}
public void updateKey(String packageName, String autocryptId, Date effectiveDate, long masterKeyId,
boolean isMutual) {
ensureAutocryptPeerExists(packageName, autocryptId);
UpdateKey updateStatement = new UpdateKey(getWritableDb(), AutocryptPeer.FACTORY);
updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, isMutual);
updateStatement.executeUpdateDelete();
getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId);
}
public void updateKeyGossip(String packageName, String autocryptId, Date effectiveDate, long masterKeyId,
GossipOrigin origin) {
ensureAutocryptPeerExists(packageName, autocryptId);
UpdateGossipKey updateStatement = new UpdateGossipKey(getWritableDb(), AutocryptPeer.FACTORY);
updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, origin);
updateStatement.executeUpdateDelete();
getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId);
}
public List<AutocryptPeer> getAutocryptPeersForKey(long masterKeyId) {
ArrayList<AutocryptPeer> result = new ArrayList<>();
SqlDelightQuery query = AutocryptPeer.FACTORY.selectByMasterKeyId(masterKeyId);
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToNext()) {
AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor);
result.add(autocryptPeer);
}
}
return result;
}
public void deleteByIdentifier(String packageName, String autocryptId) {
Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId);
DeleteByIdentifier deleteStatement = new DeleteByIdentifier(getReadableDb());
deleteStatement.bind(packageName, autocryptId);
deleteStatement.execute();
if (masterKeyId != null) {
getDatabaseNotifyManager().notifyAutocryptDelete(autocryptId, masterKeyId);
}
}
public void deleteByMasterKeyId(long masterKeyId) {
DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getReadableDb());
deleteStatement.bind(masterKeyId);
deleteStatement.execute();
}
}

View File

@@ -0,0 +1,35 @@
package org.sufficientlysecure.keychain.daos;
import android.content.Context;
import android.database.Cursor;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.model.Certification;
import org.sufficientlysecure.keychain.model.Certification.CertDetails;
public class CertificationDao extends AbstractDao {
public static CertificationDao getInstance(Context context) {
KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
return new CertificationDao(keychainDatabase, databaseNotifyManager);
}
private CertificationDao(KeychainDatabase keychainDatabase, DatabaseNotifyManager databaseNotifyManager) {
super(keychainDatabase, databaseNotifyManager);
}
public CertDetails getVerifyingCertDetails(long masterKeyId, int userPacketRank) {
SqlDelightQuery query = Certification.FACTORY.selectVerifyingCertDetails(masterKeyId, userPacketRank);
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToFirst()) {
return Certification.CERT_DETAILS_MAPPER.map(cursor);
}
}
return null;
}
}

View File

@@ -0,0 +1,66 @@
package org.sufficientlysecure.keychain.daos;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import org.sufficientlysecure.keychain.Constants;
public class DatabaseNotifyManager {
private static final Uri URI_KEYS = Uri.parse("content://" + Constants.PROVIDER_AUTHORITY + "/keys");
private static final Uri URI_APPS = Uri.parse("content://" + Constants.PROVIDER_AUTHORITY + "/apps");
private ContentResolver contentResolver;
public static DatabaseNotifyManager create(Context context) {
ContentResolver contentResolver = context.getContentResolver();
return new DatabaseNotifyManager(contentResolver);
}
private DatabaseNotifyManager(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
}
public void notifyKeyChange(long masterKeyId) {
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
contentResolver.notifyChange(uri, null);
}
public void notifyAutocryptDelete(String autocryptId, Long masterKeyId) {
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
contentResolver.notifyChange(uri, null);
}
public void notifyAutocryptUpdate(String autocryptId, long masterKeyId) {
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
contentResolver.notifyChange(uri, null);
}
public void notifyKeyMetadataChange(long masterKeyId) {
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
contentResolver.notifyChange(uri, null);
}
public void notifyApiAppChange(String apiApp) {
Uri uri = getNotifyUriPackageName(apiApp);
contentResolver.notifyChange(uri, null);
}
public static Uri getNotifyUriAllKeys() {
return URI_KEYS;
}
public static Uri getNotifyUriMasterKeyId(long masterKeyId) {
return URI_KEYS.buildUpon().appendPath(Long.toString(masterKeyId)).build();
}
public static Uri getNotifyUriAllApps() {
return URI_APPS;
}
public static Uri getNotifyUriPackageName(String packageName) {
return URI_APPS.buildUpon().appendPath(packageName).build();
}
}

View File

@@ -0,0 +1,64 @@
package org.sufficientlysecure.keychain.daos;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import android.content.Context;
import android.database.Cursor;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.KeyMetadataModel.ReplaceKeyMetadata;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.model.KeyMetadata;
public class KeyMetadataDao extends AbstractDao {
public static KeyMetadataDao create(Context context) {
KeychainDatabase database = KeychainDatabase.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
return new KeyMetadataDao(database, databaseNotifyManager);
}
private KeyMetadataDao(KeychainDatabase database, DatabaseNotifyManager databaseNotifyManager) {
super(database, databaseNotifyManager);
}
public KeyMetadata getKeyMetadata(long masterKeyId) {
SqlDelightQuery query = KeyMetadata.FACTORY.selectByMasterKeyId(masterKeyId);
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToFirst()) {
return KeyMetadata.FACTORY.selectByMasterKeyIdMapper().map(cursor);
}
}
return null;
}
public void resetAllLastUpdatedTimes() {
new KeyMetadata.DeleteAllLastUpdatedTimes(getWritableDb()).execute();
}
public void renewKeyLastUpdatedTime(long masterKeyId, boolean seenOnKeyservers) {
ReplaceKeyMetadata replaceStatement = new ReplaceKeyMetadata(getWritableDb(), KeyMetadata.FACTORY);
replaceStatement.bind(masterKeyId, new Date(), seenOnKeyservers);
replaceStatement.executeInsert();
getDatabaseNotifyManager().notifyKeyMetadataChange(masterKeyId);
}
public List<byte[]> getFingerprintsForKeysOlderThan(long olderThan, TimeUnit timeUnit) {
SqlDelightQuery query = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThan(new Date(timeUnit.toMillis(olderThan)));
List<byte[]> fingerprintList = new ArrayList<>();
try (Cursor cursor = getReadableDb().query(query)) {
while (cursor.moveToNext()) {
byte[] fingerprint = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThanMapper().map(cursor);
fingerprintList.add(fingerprint);
}
}
return fingerprintList;
}
}

View File

@@ -0,0 +1,275 @@
/*
* 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.daos;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.support.annotation.WorkerThread;
import com.squareup.sqldelight.SqlDelightQuery;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.model.Certification;
import org.sufficientlysecure.keychain.model.KeyRingPublic;
import org.sufficientlysecure.keychain.model.KeySignature;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.model.UserPacket;
import org.sufficientlysecure.keychain.model.UserPacket.UserId;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import timber.log.Timber;
@WorkerThread
public class KeyRepository extends AbstractDao {
final ContentResolver contentResolver;
final LocalPublicKeyStorage mLocalPublicKeyStorage;
final LocalSecretKeyStorage localSecretKeyStorage;
OperationLog mLog;
int mIndent;
public static KeyRepository create(Context context) {
ContentResolver contentResolver = context.getContentResolver();
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
KeychainDatabase database = KeychainDatabase.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
return new KeyRepository(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage);
}
private KeyRepository(ContentResolver contentResolver, KeychainDatabase database,
DatabaseNotifyManager databaseNotifyManager,
LocalPublicKeyStorage localPublicKeyStorage,
LocalSecretKeyStorage localSecretKeyStorage) {
this(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0);
}
KeyRepository(ContentResolver contentResolver, KeychainDatabase database,
DatabaseNotifyManager databaseNotifyManager,
LocalPublicKeyStorage localPublicKeyStorage,
LocalSecretKeyStorage localSecretKeyStorage,
OperationLog log, int indent) {
super(database, databaseNotifyManager);
this.contentResolver = contentResolver;
mLocalPublicKeyStorage = localPublicKeyStorage;
this.localSecretKeyStorage = localSecretKeyStorage;
mIndent = indent;
mLog = log;
}
public OperationLog getLog() {
return mLog;
}
public void log(LogType type) {
if (mLog != null) {
mLog.add(type, mIndent);
}
}
public void log(LogType type, Object... parameters) {
if (mLog != null) {
mLog.add(type, mIndent, parameters);
}
}
public void clearLog() {
mLog = new OperationLog();
}
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long masterKeyId) throws NotFoundException {
UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId);
if (unifiedKeyInfo == null) {
throw new NotFoundException();
}
byte[] publicKeyData = loadPublicKeyRingData(masterKeyId);
return new CanonicalizedPublicKeyRing(publicKeyData, unifiedKeyInfo.verified());
}
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long masterKeyId) throws NotFoundException {
UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId);
if (unifiedKeyInfo == null || !unifiedKeyInfo.has_any_secret()) {
throw new NotFoundException();
}
byte[] secretKeyData = loadSecretKeyRingData(masterKeyId);
if (secretKeyData == null) {
throw new IllegalStateException("Missing expected secret key data!");
}
return new CanonicalizedSecretKeyRing(secretKeyData, unifiedKeyInfo.verified());
}
public List<Long> getAllMasterKeyIds() {
SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds();
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map);
}
public List<Long> getMasterKeyIdsBySigner(List<Long> signerMasterKeyIds) {
long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds);
SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds);
return mapAllRows(query, KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper()::map);
}
public Long getMasterKeyIdBySubkeyId(long subKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId);
return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper()::map);
}
public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId);
return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
}
public List<UnifiedKeyInfo> getUnifiedKeyInfo(long... masterKeyIds) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds);
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
}
public List<UnifiedKeyInfo> getUnifiedKeyInfosByMailAddress(String mailAddress) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%');
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
}
public List<UnifiedKeyInfo> getAllUnifiedKeyInfo() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
}
public List<UnifiedKeyInfo> getAllUnifiedKeyInfoWithSecret() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret();
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
}
public List<UserId> getUserIds(long... masterKeyIds) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds);
return mapAllRows(query, UserPacket.USER_ID_MAPPER::map);
}
public List<String> getConfirmedUserIds(long masterKeyId) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(
Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET);
return mapAllRows(query, (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id());
}
public List<SubKey> getSubKeysByMasterKeyId(long masterKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId);
return mapAllRows(query, SubKey.SUBKEY_MAPPER::map);
}
public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId);
return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER::map);
}
public byte[] getFingerprintByKeyId(long keyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectFingerprintByKeyId(keyId);
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper()::map);
}
private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
aos.write(data);
aos.close();
return bos.toByteArray();
}
public String getPublicKeyRingAsArmoredString(long masterKeyId) throws NotFoundException, IOException {
byte[] data = loadPublicKeyRingData(masterKeyId);
byte[] armoredData = getKeyRingAsArmoredData(data);
return new String(armoredData);
}
public byte[] getSecretKeyRingAsArmoredData(long masterKeyId) throws NotFoundException, IOException {
byte[] data = loadSecretKeyRingData(masterKeyId);
return getKeyRingAsArmoredData(data);
}
public ContentResolver getContentResolver() {
return contentResolver;
}
public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException {
SqlDelightQuery query = KeyRingPublic.FACTORY.selectByMasterKeyId(masterKeyId);
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToFirst()) {
KeyRingPublic keyRingPublic = KeyRingPublic.MAPPER.map(cursor);
byte[] keyRingData = keyRingPublic.key_ring_data();
if (keyRingData == null) {
keyRingData = mLocalPublicKeyStorage.readPublicKey(masterKeyId);
}
return keyRingData;
}
} catch (IOException e) {
Timber.e(e, "Error reading public key from storage!");
}
throw new NotFoundException();
}
public final byte[] loadSecretKeyRingData(long masterKeyId) throws NotFoundException {
try {
return localSecretKeyStorage.readSecretKey(masterKeyId);
} catch (IOException e) {
Timber.e(e, "Error reading secret key from storage!");
throw new NotFoundException();
}
}
public long getSecretSignId(long masterKeyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId);
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map);
}
public long getSecretAuthenticationId(long masterKeyId) throws NotFoundException {
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId);
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map);
}
public static class NotFoundException extends Exception {
public NotFoundException() {
}
public NotFoundException(String name) {
super(name);
}
}
private long[] getLongListAsArray(List<Long> longList) {
long[] longs = new long[longList.size()];
int i = 0;
for (Long aLong : longList) {
longs[i++] = aLong;
}
return longs;
}
}

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.provider;
package org.sufficientlysecure.keychain.daos;
import java.io.IOException;
@@ -29,20 +29,23 @@ import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v4.util.LongSparseArray;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.CustomColumnAdapters;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.operations.results.UpdateTrustResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
@@ -55,13 +58,11 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -84,69 +85,56 @@ public class KeyWritableRepository extends KeyRepository {
private static final int MAX_CACHED_KEY_SIZE = 1024 * 50;
private final Context context;
private final LastUpdateInteractor lastUpdateInteractor;
private DatabaseNotifyManager databaseNotifyManager;
private final DatabaseNotifyManager databaseNotifyManager;
private AutocryptPeerDao autocryptPeerDao;
public static KeyWritableRepository create(Context context) {
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
LastUpdateInteractor lastUpdateInteractor = LastUpdateInteractor.create(context);
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context);
KeychainDatabase database = KeychainDatabase.getInstance(context);
return new KeyWritableRepository(context, localPublicKeyStorage, lastUpdateInteractor,
databaseNotifyManager);
return new KeyWritableRepository(context, database,
localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, autocryptPeerDao);
}
private KeyWritableRepository(Context context,
KeychainDatabase database, LocalPublicKeyStorage localPublicKeyStorage,
LocalSecretKeyStorage localSecretKeyStorage,
DatabaseNotifyManager databaseNotifyManager, AutocryptPeerDao autocryptPeerDao) {
this(context, database, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0,
autocryptPeerDao);
}
@VisibleForTesting
KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage,
LastUpdateInteractor lastUpdateInteractor, DatabaseNotifyManager databaseNotifyManager) {
this(context, localPublicKeyStorage, lastUpdateInteractor, new OperationLog(), 0,
databaseNotifyManager);
}
private KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage,
LastUpdateInteractor lastUpdateInteractor, OperationLog log, int indent,
DatabaseNotifyManager databaseNotifyManager) {
super(context.getContentResolver(), localPublicKeyStorage, log, indent);
private KeyWritableRepository(Context context, KeychainDatabase database,
LocalPublicKeyStorage localPublicKeyStorage,
LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager,
OperationLog log, int indent, AutocryptPeerDao autocryptPeerDao) {
super(context.getContentResolver(), database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage, log, indent);
this.context = context;
this.databaseNotifyManager = databaseNotifyManager;
this.lastUpdateInteractor = lastUpdateInteractor;
this.autocryptPeerDao = autocryptPeerDao;
}
private LongSparseArray<CanonicalizedPublicKey> getTrustedMasterKeys() {
Cursor cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] {
KeyRings.MASTER_KEY_ID,
// we pick from cache only information that is not easily available from keyrings
KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED
}, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<>();
try {
LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<>();
if (cursor == null) {
return result;
}
while (cursor.moveToNext()) {
try {
long masterKeyId = cursor.getLong(0);
int verified = cursor.getInt(2);
byte[] blob = loadPublicKeyRingData(masterKeyId);
if (blob != null) {
result.put(masterKeyId, new CanonicalizedPublicKeyRing(blob, verified).getPublicKey());
}
} catch (NotFoundException e) {
throw new IllegalStateException("Error reading secret key data, this should not happen!", e);
List<UnifiedKeyInfo> unifiedKeyInfoWithSecret = getAllUnifiedKeyInfoWithSecret();
for (UnifiedKeyInfo unifiedKeyInfo : unifiedKeyInfoWithSecret) {
try {
byte[] blob = loadPublicKeyRingData(unifiedKeyInfo.master_key_id());
if (blob != null) {
result.put(unifiedKeyInfo.master_key_id(),
new CanonicalizedPublicKeyRing(blob, unifiedKeyInfo.verified()).getPublicKey());
}
}
return result;
} finally {
if (cursor != null) {
cursor.close();
} catch (NotFoundException e) {
throw new IllegalStateException("Error reading secret key data, this should not happen!", e);
}
}
return result;
}
// bits, in order: CESA. make SURE these are correct, we will get bad log entries otherwise!!
@@ -322,15 +310,17 @@ public class KeyWritableRepository extends KeyRepository {
}
// do we have a trusted key for this?
if (trustedKeys.indexOfKey(certId) < 0) {
if (!signerKeyIds.contains(certId)) {
operations.add(ContentProviderOperation.newInsert(KeySignatures.CONTENT_URI)
.withValue(KeySignatures.MASTER_KEY_ID, masterKeyId)
.withValue(KeySignatures.SIGNER_KEY_ID, certId)
.build());
signerKeyIds.add(certId);
}
// keep a note about the issuer of this key signature
if (!signerKeyIds.contains(certId)) {
operations.add(ContentProviderOperation.newInsert(KeySignatures.CONTENT_URI)
.withValue(KeySignatures.MASTER_KEY_ID, masterKeyId)
.withValue(KeySignatures.SIGNER_KEY_ID, certId)
.build());
signerKeyIds.add(certId);
}
boolean isSignatureFromTrustedKey = trustedKeys.indexOfKey(certId) >= 0;
if (!isSignatureFromTrustedKey) {
unknownCerts += 1;
continue;
}
@@ -495,7 +485,7 @@ public class KeyWritableRepository extends KeyRepository {
if (item.selfRevocation != null) {
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation,
Certs.VERIFIED_SELF));
VerificationStatus.VERIFIED_SELF));
// don't bother with trusted certs if the uid is revoked, anyways
continue;
}
@@ -505,7 +495,7 @@ public class KeyWritableRepository extends KeyRepository {
}
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
selfCertsAreTrusted ? VerificationStatus.VERIFIED_SECRET : VerificationStatus.VERIFIED_SELF));
// iterate over signatures
for (int i = 0; i < item.trustedCerts.size(); i++) {
@@ -517,7 +507,7 @@ public class KeyWritableRepository extends KeyRepository {
}
// otherwise, build database operation
operations.add(buildCertOperations(
masterKeyId, userIdRank, sig, Certs.VERIFIED_SECRET));
masterKeyId, userIdRank, sig, VerificationStatus.VERIFIED_SECRET));
}
}
@@ -529,16 +519,13 @@ public class KeyWritableRepository extends KeyRepository {
mIndent -= 1;
}
ContentProviderOperation lastUpdateReinsertOp = getLastUpdatedReinsertOperationByMasterKeyId(masterKeyId);
if (lastUpdateReinsertOp != null) {
operations.add(lastUpdateReinsertOp);
}
try {
// delete old version of this keyRing (from database only!), which also deletes all keys and userIds on cascade
int deleted = contentResolver.delete(
KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null);
if (deleted > 0) {
DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getWritableDb());
deleteStatement.bind(masterKeyId);
int deletedRows = deleteStatement.executeUpdateDelete();
if (deletedRows > 0) {
log(LogType.MSG_IP_DELETE_OLD_OK);
result |= SaveKeyringResult.UPDATED;
} else {
@@ -564,26 +551,6 @@ public class KeyWritableRepository extends KeyRepository {
}
private ContentProviderOperation getLastUpdatedReinsertOperationByMasterKeyId(long masterKeyId) {
Long lastUpdateTime = getLastUpdateTime(masterKeyId);
if (lastUpdateTime == null) {
return null;
}
Boolean seenOnKeyservers = lastUpdateInteractor.getSeenOnKeyservers(masterKeyId);
ContentValues lastUpdatedEntry = new ContentValues(2);
lastUpdatedEntry.put(UpdatedKeys.MASTER_KEY_ID, masterKeyId);
lastUpdatedEntry.put(UpdatedKeys.LAST_UPDATED, lastUpdateTime);
if (seenOnKeyservers != null){
lastUpdatedEntry.put(UpdatedKeys.SEEN_ON_KEYSERVERS, seenOnKeyservers);
}
return ContentProviderOperation
.newInsert(UpdatedKeys.CONTENT_URI)
.withValues(lastUpdatedEntry)
.build();
}
private void writePublicKeyRing(CanonicalizedPublicKeyRing keyRing, long masterKeyId,
ArrayList<ContentProviderOperation> operations) throws IOException {
byte[] encodedKey = keyRing.getEncoded();
@@ -601,27 +568,24 @@ public class KeyWritableRepository extends KeyRepository {
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
}
private Uri writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException {
private void writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException {
byte[] encodedKey = keyRing.getEncoded();
ContentValues values = new ContentValues();
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
values.put(KeyRingData.KEY_RING_DATA, encodedKey);
// insert new version of this keyRing
Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId);
return contentResolver.insert(uri, values);
localSecretKeyStorage.writeSecretKey(masterKeyId, encodedKey);
}
public boolean deleteKeyRing(long masterKeyId) {
try {
mLocalPublicKeyStorage.deletePublicKey(masterKeyId);
localSecretKeyStorage.deleteSecretKey(masterKeyId);
} catch (IOException e) {
Timber.e(e, "Could not delete file!");
return false;
}
contentResolver.delete(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId),null, null);
int deletedRows = contentResolver.delete(KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null);
autocryptPeerDao.deleteByMasterKeyId(masterKeyId);
DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getWritableDb());
deleteStatement.bind(masterKeyId);
int deletedRows = deleteStatement.executeUpdateDelete();
databaseNotifyManager.notifyKeyChange(masterKeyId);
@@ -683,11 +647,7 @@ public class KeyWritableRepository extends KeyRepository {
// save secret keyring
try {
Uri insertedUri = writeSecretKeyRing(keyRing, masterKeyId);
if (insertedUri == null) {
log(LogType.MSG_IS_DB_EXCEPTION);
return SaveKeyringResult.RESULT_ERROR;
}
writeSecretKeyRing(keyRing, masterKeyId);
} catch (IOException e) {
Timber.e(e, "Failed to encode key!");
log(LogType.MSG_IS_ERROR_IO_EXC);
@@ -1039,30 +999,18 @@ public class KeyWritableRepository extends KeyRepository {
log.add(LogType.MSG_TRUST, 0);
Cursor cursor;
Preferences preferences = Preferences.getPreferences(context);
boolean isTrustDbInitialized = preferences.isKeySignaturesTableInitialized();
List<Long> masterKeyIds;
if (!isTrustDbInitialized) {
log.add(LogType.MSG_TRUST_INITIALIZE, 1);
cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsUri(),
new String[] { KeyRings.MASTER_KEY_ID }, null, null, null);
masterKeyIds = getAllMasterKeyIds();
} else {
String[] signerMasterKeyIdStrings = new String[signerMasterKeyIds.size()];
int i = 0;
for (Long masterKeyId : signerMasterKeyIds) {
log.add(LogType.MSG_TRUST_KEY, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId));
signerMasterKeyIdStrings[i++] = Long.toString(masterKeyId);
}
cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsFilterBySigner(),
new String[] { KeyRings.MASTER_KEY_ID }, null, signerMasterKeyIdStrings, null);
masterKeyIds = getMasterKeyIdsBySigner(signerMasterKeyIds);
}
if (cursor == null) {
throw new IllegalStateException();
}
int totalKeys = cursor.getCount();
int totalKeys = masterKeyIds.size();
int processedKeys = 0;
if (totalKeys == 0) {
@@ -1072,41 +1020,37 @@ public class KeyWritableRepository extends KeyRepository {
log.add(LogType.MSG_TRUST_COUNT, 1, totalKeys);
}
try {
while (cursor.moveToNext()) {
try {
long masterKeyId = cursor.getLong(0);
for (long masterKeyId : masterKeyIds) {
try {
log.add(LogType.MSG_TRUST_KEY, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId));
byte[] pubKeyData = loadPublicKeyRingData(masterKeyId);
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(pubKeyData);
byte[] pubKeyData = loadPublicKeyRingData(masterKeyId);
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(pubKeyData);
clearLog();
SaveKeyringResult result = savePublicKeyRing(uncachedKeyRing, true);
clearLog();
SaveKeyringResult result = savePublicKeyRing(uncachedKeyRing, true);
log.add(result, 1);
progress.setProgress(processedKeys++, totalKeys);
} catch (NotFoundException | PgpGeneralException | IOException e) {
Timber.e(e, "Error updating trust database");
return new UpdateTrustResult(UpdateTrustResult.RESULT_ERROR, log);
}
log.add(result, 1);
progress.setProgress(processedKeys++, totalKeys);
} catch (NotFoundException | PgpGeneralException | IOException e) {
Timber.e(e, "Error updating trust database");
return new UpdateTrustResult(UpdateTrustResult.RESULT_ERROR, log);
}
if (!isTrustDbInitialized) {
preferences.setKeySignaturesTableInitialized();
}
log.add(LogType.MSG_TRUST_OK, 1);
return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log);
} finally {
cursor.close();
}
if (!isTrustDbInitialized) {
preferences.setKeySignaturesTableInitialized();
}
log.add(LogType.MSG_TRUST_OK, 1);
return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log);
}
/**
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
*/
private ContentProviderOperation
buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, int verified)
buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, VerificationStatus verificationStatus)
throws IOException {
ContentValues values = new ContentValues();
values.put(Certs.MASTER_KEY_ID, masterKeyId);
@@ -1114,7 +1058,7 @@ public class KeyWritableRepository extends KeyRepository {
values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyId());
values.put(Certs.TYPE, cert.getSignatureType());
values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000);
values.put(Certs.VERIFIED, verified);
values.put(Certs.VERIFIED, CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.encode(verificationStatus));
values.put(Certs.DATA, cert.getEncoded());
Uri uri = Certs.buildCertsUri(masterKeyId);

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.provider;
package org.sufficientlysecure.keychain.daos;
import java.io.ByteArrayOutputStream;

View File

@@ -0,0 +1,105 @@
/*
* 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.daos;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.content.Context;
import okhttp3.internal.Util;
public class LocalSecretKeyStorage {
private static final String FORMAT_STR_SECRET_KEY = "0x%016x.sec";
private static final String SECRET_KEYS_DIR_NAME = "secret_keys";
private final File localSecretKeysDir;
public static LocalSecretKeyStorage getInstance(Context context) {
File localSecretKeysDir = new File(context.getFilesDir(), SECRET_KEYS_DIR_NAME);
return new LocalSecretKeyStorage(localSecretKeysDir);
}
private LocalSecretKeyStorage(File localSecretKeysDir) {
this.localSecretKeysDir = localSecretKeysDir;
}
private File getSecretKeyFile(long masterKeyId) throws IOException {
if (!localSecretKeysDir.exists()) {
localSecretKeysDir.mkdir();
}
if (!localSecretKeysDir.isDirectory()) {
throw new IOException("Failed creating public key directory!");
}
String keyFilename = String.format(FORMAT_STR_SECRET_KEY, masterKeyId);
return new File(localSecretKeysDir, keyFilename);
}
public void writeSecretKey(long masterKeyId, byte[] encoded) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
FileOutputStream fileOutputStream = new FileOutputStream(publicKeyFile);
try {
fileOutputStream.write(encoded);
} finally {
Util.closeQuietly(fileOutputStream);
}
}
byte[] readSecretKey(long masterKeyId) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
try {
FileInputStream fileInputStream = new FileInputStream(publicKeyFile);
return readIntoByteArray(fileInputStream);
} catch (FileNotFoundException e) {
return null;
}
}
private static byte[] readIntoByteArray(FileInputStream fileInputStream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[128];
int bytesRead;
while ((bytesRead = fileInputStream.read(buf)) != -1) {
baos.write(buf, 0, bytesRead);
}
return baos.toByteArray();
}
void deleteSecretKey(long masterKeyId) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
if (publicKeyFile.exists()) {
boolean deleteSuccess = publicKeyFile.delete();
if (!deleteSuccess) {
throw new IOException("File exists, but could not be deleted!");
}
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.daos;
import android.content.Context;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.OverriddenWarningsModel.DeleteByIdentifier;
import org.sufficientlysecure.keychain.OverriddenWarningsModel.InsertIdentifier;
import org.sufficientlysecure.keychain.model.OverriddenWarning;
public class OverriddenWarningsDao extends AbstractDao {
public static OverriddenWarningsDao create(Context context) {
KeychainDatabase database = KeychainDatabase.getInstance(context);
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
return new OverriddenWarningsDao(database, databaseNotifyManager);
}
private OverriddenWarningsDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) {
super(db, databaseNotifyManager);
}
public boolean isWarningOverridden(String identifier) {
SqlDelightQuery query = OverriddenWarning.FACTORY.selectCountByIdentifier(identifier);
Long result = mapSingleRow(query, OverriddenWarning.FACTORY.selectCountByIdentifierMapper()::map);
return result != null && result > 0;
}
public void putOverride(String identifier) {
InsertIdentifier statement = new InsertIdentifier(getWritableDb());
statement.bind(identifier);
statement.executeInsert();
}
public void deleteOverride(String identifier) {
DeleteByIdentifier statement = new DeleteByIdentifier(getWritableDb());
statement.bind(identifier);
statement.executeInsert();
}
}

View File

@@ -123,11 +123,13 @@ public class ImportKeysListLoader
}
} catch (IOException e) {
Timber.e(e, "IOException on parsing key file! Return NoValidKeysException!");
OperationResult.OperationLog log = new OperationResult.OperationLog();
log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0);
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log);
mData.clear();
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
if (mData.isEmpty()) {
OperationResult.OperationLog log = new OperationResult.OperationLog();
log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0);
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log);
mData.clear();
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.keysync;
import java.util.concurrent.TimeUnit;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
public class KeyserverSyncManager {
private static final long SYNC_INTERVAL = 3;
private static final TimeUnit SYNC_INTERVAL_UNIT = TimeUnit.DAYS;
private static final String PERIODIC_WORK_TAG = "keyserverSync";
public static void updateKeyserverSyncSchedule(Context context, boolean forceReschedule) {
Preferences prefs = Preferences.getPreferences(context);
if (!forceReschedule && prefs.isKeyserverSyncScheduled() != prefs.isKeyserverSyncEnabled()) {
return;
}
WorkManager workManager = WorkManager.getInstance();
if (workManager == null) {
Timber.e("WorkManager unavailable!");
return;
}
workManager.cancelAllWorkByTag(PERIODIC_WORK_TAG);
if (!prefs.isKeyserverSyncEnabled()) {
return;
}
/* Periodic syncs can't be unique, so we just use this to launch a uniquely queued worker */
Builder constraints = new Builder()
.setRequiredNetworkType(prefs.getWifiOnlySync() ? NetworkType.UNMETERED : NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true);
if (VERSION.SDK_INT >= VERSION_CODES.M) {
constraints.setRequiresDeviceIdle(true);
}
PeriodicWorkRequest workRequest =
new PeriodicWorkRequest.Builder(KeyserverSyncWorker.class, SYNC_INTERVAL, SYNC_INTERVAL_UNIT)
.setConstraints(constraints.build())
.addTag(PERIODIC_WORK_TAG)
.build();
workManager.enqueue(workRequest);
prefs.setKeyserverSyncScheduled(true);
}
public static void debugRunSyncNow() {
WorkManager workManager = WorkManager.getInstance();
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(KeyserverSyncWorker.class).build();
workManager.enqueue(workRequest);
}
}

View File

@@ -0,0 +1,145 @@
package org.sufficientlysecure.keychain.keysync;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.os.CancellationSignal;
import androidx.work.Worker;
import org.sufficientlysecure.keychain.Constants.NotificationChannels;
import org.sufficientlysecure.keychain.Constants.NotificationIds;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.network.orbot.OrbotHelper;
import org.sufficientlysecure.keychain.operations.KeySyncOperation;
import org.sufficientlysecure.keychain.operations.KeySyncParcel;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
import org.sufficientlysecure.keychain.util.ResourceUtils;
import timber.log.Timber;
public class KeyserverSyncWorker extends Worker {
private CancellationSignal cancellationSignal = new CancellationSignal();
@NonNull
@Override
public WorkerResult doWork() {
KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(getApplicationContext());
Timber.d("Starting key sync…");
Progressable notificationProgressable = notificationShowForProgress();
KeySyncOperation keySync = new KeySyncOperation(getApplicationContext(), keyWritableRepository, notificationProgressable, cancellationSignal);
ImportKeyResult result = keySync.execute(KeySyncParcel.createRefreshOutdated(), CryptoInputParcel.createCryptoInputParcel());
return handleUpdateResult(result);
}
/**
* Since we're returning START_REDELIVER_INTENT in onStartCommand, we need to remember to call
* stopSelf(int) to prevent the Intent from being redelivered if our work is already done
*
* @param result
* result of keyserver sync
*/
private WorkerResult 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
// try to start it silently, if disabled show notifications
new OrbotHelper.SilentStartManager() {
@Override
protected void onOrbotStarted() {
}
@Override
protected void onSilentStartDisabled() {
OrbotRequiredDialogActivity.showOrbotRequiredNotification(getApplicationContext());
}
}.startOrbotAndListen(getApplicationContext(), false);
return WorkerResult.RETRY;
} else if (isStopped()) {
Timber.d("Keyserver sync cancelled");
return WorkerResult.FAILURE;
} else {
Timber.d("Keyserver sync completed: Updated: %d, Failed: %d", result.mUpdatedKeys, result.mBadKeys);
return WorkerResult.SUCCESS;
}
}
private Progressable notificationShowForProgress() {
final Context context = getApplicationContext();
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return null;
}
createNotificationChannelsIfNecessary(context, notificationManager);
NotificationCompat.Builder builder = new Builder(context, NotificationChannels.KEYSERVER_SYNC)
.setSmallIcon(R.drawable.ic_stat_notify_24dp)
.setLargeIcon(ResourceUtils.getDrawableAsNotificationBitmap(context, R.mipmap.ic_launcher))
.setContentTitle(context.getString(R.string.notify_title_keysync))
.setPriority(NotificationCompat.PRIORITY_MIN)
.setTimeoutAfter(5000)
.setVibrate(null)
.setSound(null)
.setProgress(0, 0, true);
return new Progressable() {
@Override
public void setProgress(String message, int current, int total) {
setProgress(current, total);
}
@Override
public void setProgress(int resourceId, int current, int total) {
setProgress(current, total);
}
@Override
public void setProgress(int current, int total) {
if (total == 0) {
notificationManager.cancel(NotificationIds.KEYSERVER_SYNC);
return;
}
builder.setProgress(total, current, false);
if (current == total) {
builder.setContentTitle(context.getString(R.string.notify_title_keysync_finished, total));
builder.setContentText(null);
} else {
builder.setContentText(context.getString(R.string.notify_content_keysync, current, total));
}
notificationManager.notify(NotificationIds.KEYSERVER_SYNC, builder.build());
}
@Override
public void setPreventCancel() {
}
};
}
private void createNotificationChannelsIfNecessary(Context context,
NotificationManager notificationManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = context.getString(R.string.notify_channel_keysync);
NotificationChannel channel = new NotificationChannel(
NotificationChannels.KEYSERVER_SYNC, name, NotificationManager.IMPORTANCE_MIN);
notificationManager.createNotificationChannel(channel);
}
}
@Override
public void onStopped() {
super.onStopped();
cancellationSignal.cancel();
}
}

View File

@@ -0,0 +1,113 @@
package org.sufficientlysecure.keychain.livedata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.livedata.ApiAppsLiveData.ListedApp;
import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
public class ApiAppsLiveData extends AsyncTaskLiveData<List<ListedApp>> {
private final ApiAppDao apiAppDao;
private final PackageManager packageManager;
public ApiAppsLiveData(Context context) {
super(context, DatabaseNotifyManager.getNotifyUriAllApps());
packageManager = getContext().getPackageManager();
apiAppDao = ApiAppDao.getInstance(context);
}
@Override
protected List<ListedApp> asyncLoadData() {
ArrayList<ListedApp> result = new ArrayList<>();
loadRegisteredApps(result);
addPlaceholderApps(result);
Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName));
return result;
}
private void loadRegisteredApps(ArrayList<ListedApp> result) {
List<ApiApp> registeredApiApps = apiAppDao.getAllApiApps();
for (ApiApp apiApp : registeredApiApps) {
ListedApp listedApp;
try {
ApplicationInfo ai = packageManager.getApplicationInfo(apiApp.package_name(), 0);
CharSequence applicationLabel = packageManager.getApplicationLabel(ai);
Drawable applicationIcon = packageManager.getApplicationIcon(ai);
listedApp = new ListedApp(apiApp.package_name(), true, true, applicationLabel, applicationIcon, null);
} catch (PackageManager.NameNotFoundException e) {
listedApp = new ListedApp(apiApp.package_name(), false, true, apiApp.package_name(), null, null);
}
result.add(listedApp);
}
}
private void addPlaceholderApps(ArrayList<ListedApp> result) {
for (ListedApp placeholderApp : PLACERHOLDER_APPS) {
if (!containsByPackageName(result, placeholderApp.packageName)) {
try {
packageManager.getApplicationInfo(placeholderApp.packageName, 0);
result.add(placeholderApp.withIsInstalled());
} catch (PackageManager.NameNotFoundException e) {
result.add(placeholderApp);
}
}
}
}
private boolean containsByPackageName(ArrayList<ListedApp> result, String packageName) {
for (ListedApp app : result) {
if (packageName.equals(app.packageName)) {
return true;
}
}
return false;
}
public static class ListedApp {
public final String packageName;
public final boolean isInstalled;
public final boolean isRegistered;
public final String readableName;
public final Drawable applicationIcon;
public final Integer applicationIconRes;
ListedApp(String packageName, boolean isInstalled, boolean isRegistered, CharSequence readableName,
Drawable applicationIcon, Integer applicationIconRes) {
this.packageName = packageName;
this.isInstalled = isInstalled;
this.isRegistered = isRegistered;
this.readableName = readableName.toString();
this.applicationIcon = applicationIcon;
this.applicationIconRes = applicationIconRes;
}
public ListedApp withIsInstalled() {
return new ListedApp(packageName, true, isRegistered, readableName, applicationIcon, applicationIconRes);
}
}
private static final ListedApp[] PLACERHOLDER_APPS = {
new ListedApp("com.fsck.k9", false, false, "K-9 Mail", null, R.drawable.apps_k9),
new ListedApp("com.zeapo.pwdstore", false, false, "Password Store", null, R.drawable.apps_password_store),
new ListedApp("eu.siacs.conversations", false, false, "Conversations (Instant Messaging)", null,
R.drawable.apps_conversations)
};
}

View File

@@ -0,0 +1,37 @@
package org.sufficientlysecure.keychain.livedata;
import android.content.Context;
import android.net.Uri;
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
public class GenericLiveData<T> extends AsyncTaskLiveData<T> {
private GenericDataLoader<T> genericDataLoader;
public GenericLiveData(Context context, GenericDataLoader<T> genericDataLoader) {
super(context, null);
this.genericDataLoader = genericDataLoader;
}
public GenericLiveData(Context context, Uri notifyUri, GenericDataLoader<T> genericDataLoader) {
super(context, notifyUri);
this.genericDataLoader = genericDataLoader;
}
public GenericLiveData(Context context, long notifyMasterKeyId, GenericDataLoader<T> genericDataLoader) {
super(context, DatabaseNotifyManager.getNotifyUriMasterKeyId(notifyMasterKeyId));
this.genericDataLoader = genericDataLoader;
}
@Override
protected T asyncLoadData() {
return genericDataLoader.loadData();
}
public interface GenericDataLoader<T> {
T loadData();
}
}

View File

@@ -1,145 +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.livedata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.util.DatabaseUtil;
public class KeyInfoInteractor {
// These are the rows that we will retrieve.
private String[] QUERY_PROJECTION = new String[]{
KeyRings._ID,
KeyRings.MASTER_KEY_ID,
KeyRings.CREATION,
KeyRings.HAS_ENCRYPT,
KeyRings.HAS_AUTHENTICATE_SECRET,
KeyRings.HAS_ANY_SECRET,
KeyRings.VERIFIED,
KeyRings.NAME,
KeyRings.EMAIL,
KeyRings.COMMENT,
KeyRings.IS_EXPIRED,
KeyRings.IS_REVOKED,
};
private static final int INDEX_MASTER_KEY_ID = 1;
private static final int INDEX_CREATION = 2;
private static final int INDEX_HAS_ENCRYPT = 3;
private static final int INDEX_HAS_AUTHENTICATE = 4;
private static final int INDEX_HAS_ANY_SECRET = 5;
private static final int INDEX_VERIFIED = 6;
private static final int INDEX_NAME = 7;
private static final int INDEX_EMAIL = 8;
private static final int INDEX_COMMENT = 9;
private static final String QUERY_WHERE_ALL = Tables.KEYS + "." + KeyRings.IS_REVOKED +
" = 0 AND " + KeyRings.IS_EXPIRED + " = 0";
private static final String QUERY_WHERE_SECRET = Tables.KEYS + "." + KeyRings.IS_REVOKED +
" = 0 AND " + KeyRings.IS_EXPIRED + " = 0" + " AND " + KeyRings.HAS_ANY_SECRET + " != 0";
private static final String QUERY_ORDER = Tables.KEYS + "." + KeyRings.CREATION + " DESC";
private final ContentResolver contentResolver;
public KeyInfoInteractor(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
}
public List<KeyInfo> loadKeyInfo(KeySelector keySelector) {
ArrayList<KeyInfo> keyInfos = new ArrayList<>();
Cursor cursor;
String selection = keySelector.isOnlySecret() ? QUERY_WHERE_SECRET : QUERY_WHERE_ALL;
String additionalSelection = keySelector.getSelection();
selection = DatabaseUtil.concatenateWhere(selection, additionalSelection);
cursor = contentResolver.query(keySelector.getKeyRingUri(), QUERY_PROJECTION, selection, null, QUERY_ORDER);
if (cursor == null) {
return null;
}
while (cursor.moveToNext()) {
KeyInfo keyInfo = KeyInfo.fromCursor(cursor);
keyInfos.add(keyInfo);
}
return Collections.unmodifiableList(keyInfos);
}
@AutoValue
public abstract static class KeyInfo {
public abstract long getMasterKeyId();
public abstract long getCreationDate();
public abstract boolean getHasEncrypt();
public abstract boolean getHasAuthenticate();
public abstract boolean getHasAnySecret();
public abstract boolean getIsVerified();
@Nullable
public abstract String getName();
@Nullable
public abstract String getEmail();
@Nullable
public abstract String getComment();
static KeyInfo fromCursor(Cursor cursor) {
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
long creationDate = cursor.getLong(INDEX_CREATION) * 1000L;
boolean hasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0;
boolean hasAuthenticate = cursor.getInt(INDEX_HAS_AUTHENTICATE) != 0;
boolean hasAnySecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
boolean isVerified = cursor.getInt(INDEX_VERIFIED) == 2;
String name = cursor.getString(INDEX_NAME);
String email = cursor.getString(INDEX_EMAIL);
String comment = cursor.getString(INDEX_COMMENT);
return new AutoValue_KeyInfoInteractor_KeyInfo(
masterKeyId, creationDate, hasEncrypt, hasAuthenticate, hasAnySecret, isVerified, name, email, comment);
}
}
@AutoValue
public abstract static class KeySelector {
public abstract Uri getKeyRingUri();
@Nullable
public abstract String getSelection();
public abstract boolean isOnlySecret();
public static KeySelector create(Uri keyRingUri, String selection) {
return new AutoValue_KeyInfoInteractor_KeySelector(keyRingUri, selection, false);
}
public static KeySelector createOnlySecret(Uri keyRingUri, String selection) {
return new AutoValue_KeyInfoInteractor_KeySelector(keyRingUri, selection, true);
}
}
}

View File

@@ -1,38 +0,0 @@
package org.sufficientlysecure.keychain.livedata;
import java.util.List;
import android.content.ContentResolver;
import android.content.Context;
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector;
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
public class KeyInfoLiveData extends AsyncTaskLiveData<List<KeyInfo>> {
private final KeyInfoInteractor keyInfoInteractor;
private KeySelector keySelector;
public KeyInfoLiveData(Context context, ContentResolver contentResolver) {
super(context, null);
this.keyInfoInteractor = new KeyInfoInteractor(contentResolver);
}
public void setKeySelector(KeySelector keySelector) {
this.keySelector = keySelector;
updateDataInBackground();
}
@Override
protected List<KeyInfo> asyncLoadData() {
if (keySelector == null) {
return null;
}
return keyInfoInteractor.loadKeyInfo(keySelector);
}
}

View File

@@ -0,0 +1,11 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.ApiAllowedKeysModel;
@AutoValue
public abstract class ApiAllowedKey implements ApiAllowedKeysModel {
public static final Factory<ApiAllowedKey> FACTORY = new Factory<ApiAllowedKey>(AutoValue_ApiAllowedKey::new);
}

View File

@@ -15,23 +15,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.compatibility;
package org.sufficientlysecure.keychain.model;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ListView;
/**
* Bug on Android >= 4.1
* <p/>
* http://code.google.com/p/android/issues/detail?id=35885
* <p/>
* Items are not checked in layout
*/
public class ListFragmentWorkaround extends ListFragment {
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.ApiAppsModel;
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
l.setItemChecked(position, l.isItemChecked(position));
@AutoValue
public abstract class ApiApp implements ApiAppsModel {
public static final ApiAppsModel.Factory<ApiApp> FACTORY =
new ApiAppsModel.Factory<ApiApp>(AutoValue_ApiApp::new);
public static ApiApp create(String packageName, byte[] packageSignature) {
return new AutoValue_ApiApp(null, packageName, packageSignature);
}
}

View File

@@ -0,0 +1,57 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import com.squareup.sqldelight.RowMapper;
import org.sufficientlysecure.keychain.AutocryptPeersModel;
@AutoValue
public abstract class AutocryptPeer implements AutocryptPeersModel {
public enum GossipOrigin {
GOSSIP_HEADER, SIGNATURE, DEDUP
}
public static final Factory<AutocryptPeer> FACTORY = new Factory<AutocryptPeer>(AutoValue_AutocryptPeer::new,
CustomColumnAdapters.DATE_ADAPTER, CustomColumnAdapters.DATE_ADAPTER, CustomColumnAdapters.DATE_ADAPTER,
CustomColumnAdapters.GOSSIP_ORIGIN_ADAPTER);
public static final RowMapper<AutocryptPeer> PEER_MAPPER = FACTORY.selectByIdentifiersMapper();
public static final RowMapper<AutocryptKeyStatus> KEY_STATUS_MAPPER =
FACTORY.selectAutocryptKeyStatusMapper(AutoValue_AutocryptPeer_AutocryptKeyStatus::new);
@AutoValue
public static abstract class AutocryptKeyStatus implements SelectAutocryptKeyStatusModel<AutocryptPeer> {
public boolean hasGossipKey() {
return autocryptPeer().gossip_master_key_id() != null;
}
public boolean isGossipKeyRevoked() {
Boolean gossip_key_is_revoked = gossip_key_is_revoked_int();
return gossip_key_is_revoked != null && gossip_key_is_revoked;
}
public boolean isGossipKeyExpired() {
return gossip_key_is_expired_int() != 0;
}
public boolean isGossipKeyVerified() {
return gossip_key_is_verified_int() != 0;
}
public boolean isKeyRevoked() {
Boolean revoked = key_is_revoked_int();
return revoked != null && revoked;
}
public boolean isKeyExpired() {
return key_is_expired_int() != 0;
}
public boolean isKeyVerified() {
return key_is_verified_int() != 0;
}
}
}

View File

@@ -0,0 +1,20 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.CertsModel;
@AutoValue
public abstract class Certification implements CertsModel {
public static final CertsModel.Factory<Certification> FACTORY =
new CertsModel.Factory<>(AutoValue_Certification::new, CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER);
public static final SelectVerifyingCertDetailsMapper<CertDetails> CERT_DETAILS_MAPPER =
new SelectVerifyingCertDetailsMapper<>(AutoValue_Certification_CertDetails::new);
@AutoValue
public static abstract class CertDetails implements CertsModel.SelectVerifyingCertDetailsModel {
}
}

View File

@@ -0,0 +1,93 @@
package org.sufficientlysecure.keychain.model;
import java.util.Date;
import android.support.annotation.NonNull;
import com.squareup.sqldelight.ColumnAdapter;
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
public final class CustomColumnAdapters {
private CustomColumnAdapters() { }
static final ColumnAdapter<Date,Long> DATE_ADAPTER = new ColumnAdapter<Date,Long>() {
@NonNull
@Override
public Date decode(Long databaseValue) {
// Both SQLite and OpenPGP prefer a second granularity for timestamps - so we'll translate here
return new Date(databaseValue * 1000);
}
@Override
public Long encode(@NonNull Date value) {
return value.getTime() / 1000;
}
};
static final ColumnAdapter<GossipOrigin,Long> GOSSIP_ORIGIN_ADAPTER = new ColumnAdapter<GossipOrigin,Long>() {
@NonNull
@Override
public GossipOrigin decode(Long databaseValue) {
switch (databaseValue.intValue()) {
case 0: return GossipOrigin.GOSSIP_HEADER;
case 10: return GossipOrigin.SIGNATURE;
case 20: return GossipOrigin.DEDUP;
default: throw new IllegalArgumentException("Unhandled database value!");
}
}
@Override
public Long encode(@NonNull GossipOrigin value) {
switch (value) {
case GOSSIP_HEADER: return 0L;
case SIGNATURE: return 10L;
case DEDUP: return 20L;
default: throw new IllegalArgumentException("Unhandled database value!");
}
}
};
public static final ColumnAdapter<SecretKeyType,Long> SECRET_KEY_TYPE_ADAPTER = new ColumnAdapter<SecretKeyType, Long>() {
@NonNull
@Override
public SecretKeyType decode(Long databaseValue) {
return databaseValue == null ? SecretKeyType.UNAVAILABLE : SecretKeyType.fromNum(databaseValue.intValue());
}
@Override
public Long encode(@NonNull SecretKeyType value) {
return (long) value.getNum();
}
};
public static final ColumnAdapter<VerificationStatus,Long> VERIFICATON_STATUS_ADAPTER = new ColumnAdapter<VerificationStatus, Long>() {
@NonNull
@Override
public VerificationStatus decode(Long databaseValue) {
if (databaseValue == null) {
return VerificationStatus.UNVERIFIED;
}
switch (databaseValue.intValue()) {
case 0: return VerificationStatus.UNVERIFIED;
case 1: return VerificationStatus.VERIFIED_SECRET;
case 2: return VerificationStatus.VERIFIED_SELF;
default: throw new IllegalArgumentException();
}
}
@Override
public Long encode(@NonNull VerificationStatus value) {
switch (value) {
case UNVERIFIED: return 0L;
case VERIFIED_SECRET: return 1L;
case VERIFIED_SELF: return 2L;
default: throw new IllegalArgumentException();
}
}
};
}

View File

@@ -0,0 +1,24 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.KeyMetadataModel;
@AutoValue
public abstract class KeyMetadata implements KeyMetadataModel {
public static final Factory<KeyMetadata> FACTORY = new Factory<>(
AutoValue_KeyMetadata::new, CustomColumnAdapters.DATE_ADAPTER);
public boolean hasBeenUpdated() {
return last_updated() != null;
}
public boolean isPublished() {
if (last_updated() == null) {
throw new IllegalStateException("Cannot get publication state if key has never been updated!");
}
Boolean seenOnKeyservers = seen_on_keyservers();
return seenOnKeyservers != null && seenOnKeyservers;
}
}

View File

@@ -0,0 +1,12 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.KeyRingsPublicModel;
@AutoValue
public abstract class KeyRingPublic implements KeyRingsPublicModel {
public static final Factory<KeyRingPublic> FACTORY = new Factory<>(AutoValue_KeyRingPublic::new);
public static final Mapper<KeyRingPublic> MAPPER = new Mapper<>(FACTORY);
}

View File

@@ -0,0 +1,13 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.KeySignaturesModel;
@AutoValue
public abstract class KeySignature implements KeySignaturesModel {
public static final Factory<KeySignature> FACTORY = new Factory<>(AutoValue_KeySignature::new);
public static final Mapper<KeySignature> MAPPER = new Mapper<>(FACTORY);
}

View File

@@ -0,0 +1,14 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.KeySignaturesModel;
import org.sufficientlysecure.keychain.OverriddenWarningsModel;
@AutoValue
public abstract class OverriddenWarning implements OverriddenWarningsModel {
public static final Factory<OverriddenWarning> FACTORY = new Factory<>(AutoValue_OverriddenWarning::new);
public static final Mapper<OverriddenWarning> MAPPER = new Mapper<>(FACTORY);
}

View File

@@ -0,0 +1,84 @@
package org.sufficientlysecure.keychain.model;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.google.auto.value.AutoValue;
import com.squareup.sqldelight.RowMapper;
import org.sufficientlysecure.keychain.KeysModel;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
@AutoValue
public abstract class SubKey implements KeysModel {
public static final Factory<SubKey> FACTORY =
new Factory<>(AutoValue_SubKey::new, CustomColumnAdapters.SECRET_KEY_TYPE_ADAPTER);
public static final UnifiedKeyViewMapper<UnifiedKeyInfo, Certification> UNIFIED_KEY_INFO_MAPPER =
FACTORY.selectAllUnifiedKeyInfoMapper(
AutoValue_SubKey_UnifiedKeyInfo::new, Certification.FACTORY);
public static Mapper<SubKey> SUBKEY_MAPPER = new Mapper<>(FACTORY);
public static RowMapper<SecretKeyType> SKT_MAPPER = FACTORY.selectSecretKeyTypeMapper();
public boolean expires() {
return expiry() != null;
}
@AutoValue
public static abstract class UnifiedKeyInfo implements KeysModel.UnifiedKeyViewModel {
private List<String> autocryptPackageNames;
private String cachedUidSearchString;
public boolean is_expired() {
Long expiry = expiry();
return expiry != null && expiry * 1000 < System.currentTimeMillis();
}
public boolean has_any_secret() {
return has_any_secret_int() != 0;
}
public boolean is_verified() {
VerificationStatus verified = verified();
return verified != null && verified == VerificationStatus.VERIFIED_SECRET;
}
public boolean has_duplicate() {
return has_duplicate_int() != 0;
}
public List<String> autocrypt_package_names() {
if (autocryptPackageNames == null) {
String csv = autocrypt_package_names_csv();
autocryptPackageNames = csv == null ? Collections.emptyList() :
Arrays.asList(csv.split(","));
}
return autocryptPackageNames;
}
public boolean has_auth_key() {
return has_auth_key_int() != 0;
}
public boolean has_encrypt_key() {
return has_encrypt_key_int() != 0;
}
public boolean has_sign_key() {
return has_sign_key_int() != 0;
}
public String uidSearchString() {
if (cachedUidSearchString == null) {
cachedUidSearchString = user_id_list();
if (cachedUidSearchString == null) {
cachedUidSearchString = "";
}
cachedUidSearchString = cachedUidSearchString.toLowerCase();
}
return cachedUidSearchString;
}
}
}

View File

@@ -0,0 +1,31 @@
package org.sufficientlysecure.keychain.model;
import com.google.auto.value.AutoValue;
import org.sufficientlysecure.keychain.UserPacketsModel;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
@AutoValue
public abstract class UserPacket implements UserPacketsModel {
public static final Factory<UserPacket> FACTORY = new Factory<>(AutoValue_UserPacket::new);
public static final SelectUserIdsByMasterKeyIdMapper<UserId, Certification> USER_ID_MAPPER =
FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new, Certification.FACTORY);
public static final SelectUserAttributesByTypeAndMasterKeyIdMapper<UserAttribute, Certification> USER_ATTRIBUTE_MAPPER =
FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new, Certification.FACTORY);
@AutoValue
public static abstract class UserId implements SelectUserIdsByMasterKeyIdModel {
public boolean isVerified() {
return verified() == VerificationStatus.VERIFIED_SECRET;
}
}
@AutoValue
public static abstract class UserAttribute implements SelectUserAttributesByTypeAndMasterKeyIdModel {
public boolean isVerified() {
return verified() == VerificationStatus.VERIFIED_SECRET;
}
}
}

View File

@@ -1,70 +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 android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
import timber.log.Timber;
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager conn = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
boolean isTypeWifi = (networkInfo != null) &&
(networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
boolean isConnected = (networkInfo != null) && networkInfo.isConnected();
if (isTypeWifi && isConnected) {
// broadcaster receiver disabled
setWifiReceiverComponent(false, context);
Intent serviceIntent = new Intent(context, KeyserverSyncAdapterService.class);
serviceIntent.setAction(KeyserverSyncAdapterService.ACTION_SYNC_NOW);
context.startService(serviceIntent);
}
}
public void setWifiReceiverComponent(Boolean isEnabled, Context context) {
PackageManager pm = context.getPackageManager();
ComponentName compName = new ComponentName(context,
NetworkReceiver.class);
if (isEnabled) {
pm.setComponentEnabledSetting(compName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
Timber.d("Wifi Receiver is enabled!");
} else {
pm.setComponentEnabledSetting(compName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Timber.d("Wifi Receiver is disabled!");
}
}
}

View File

@@ -24,23 +24,21 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.support.v4.os.CancellationSignal;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -52,17 +50,15 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil;
import org.sufficientlysecure.keychain.util.CountingOutputStream;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil;
import org.sufficientlysecure.keychain.util.Passphrase;
import timber.log.Timber;
@@ -78,14 +74,6 @@ import timber.log.Timber;
* either the name of a file or an output uri to write to.
*/
public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
private static final String[] PROJECTION = new String[] {
KeyRings.MASTER_KEY_ID,
KeyRings.HAS_ANY_SECRET
};
private static final int INDEX_MASTER_KEY_ID = 0;
private static final int INDEX_HAS_ANY_SECRET = 1;
// this is a very simple matcher, we only need basic sanitization
private static final Pattern HEADER_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+: [^\\n]+");
@@ -95,7 +83,7 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
}
public BackupOperation(Context context, KeyRepository keyRepository,
Progressable progressable, AtomicBoolean cancelled) {
Progressable progressable, CancellationSignal cancelled) {
super(context, keyRepository, progressable, cancelled);
}
@@ -223,42 +211,36 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
OutputStream outStream, List<String> extraSecretKeyHeaders) {
// noinspection unused TODO use these in a log entry
int okSecret = 0, okPublic = 0;
int progress = 0;
Cursor cursor = queryForKeys(masterKeyIds);
if (cursor == null || !cursor.moveToFirst()) {
log.add(LogType.MSG_BACKUP_ERROR_DB, 1);
return false; // new ExportResult(ExportResult.RESULT_ERROR, log);
}
try {
int numKeys = cursor.getCount();
List<UnifiedKeyInfo> unifiedKeyInfos;
if (masterKeyIds == null) {
unifiedKeyInfos = mKeyRepository.getAllUnifiedKeyInfo();
} else {
unifiedKeyInfos = mKeyRepository.getUnifiedKeyInfo(masterKeyIds);
}
int numKeys = unifiedKeyInfos.size();
updateProgress(mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, numKeys),
0, numKeys);
// For each public masterKey id
while (!cursor.isAfterLast()) {
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
log.add(LogType.MSG_BACKUP_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId));
for (UnifiedKeyInfo keyInfo : unifiedKeyInfos) {
log.add(LogType.MSG_BACKUP_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyInfo.master_key_id()));
boolean publicKeyWriteOk = false;
if (exportPublic) {
publicKeyWriteOk = writePublicKeyToStream(masterKeyId, log, outStream);
publicKeyWriteOk = writePublicKeyToStream(keyInfo.master_key_id(), log, outStream);
if (publicKeyWriteOk) {
okPublic += 1;
}
}
if (publicKeyWriteOk || !exportPublic) {
boolean hasSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) > 0;
if (exportSecret && hasSecret) {
log.add(LogType.MSG_BACKUP_SECRET, 2, KeyFormattingUtils.beautifyKeyId(masterKeyId));
if (writeSecretKeyToStream(masterKeyId, log, outStream, extraSecretKeyHeaders)) {
if (exportSecret && keyInfo.has_any_secret()) {
log.add(LogType.MSG_BACKUP_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyInfo.master_key_id()));
if (writeSecretKeyToStream(keyInfo.master_key_id(), log, outStream, extraSecretKeyHeaders)) {
okSecret += 1;
}
extraSecretKeyHeaders = null;
@@ -266,7 +248,6 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
}
updateProgress(progress++, numKeys);
cursor.moveToNext();
}
updateProgress(R.string.progress_done, numKeys, numKeys);
@@ -281,7 +262,6 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
} catch (Exception e) {
Timber.e(e, "error closing stream");
}
cursor.close();
}
return true;
@@ -341,29 +321,4 @@ public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
}
}
private Cursor queryForKeys(long[] masterKeyIds) {
String selection = null, selectionArgs[] = null;
if (masterKeyIds != null) {
// convert long[] to String[]
selectionArgs = new String[masterKeyIds.length];
for (int i = 0; i < masterKeyIds.length; i++) {
selectionArgs[i] = Long.toString(masterKeyIds[i]);
}
// generates ?,?,? as placeholders for selectionArgs
String placeholders = TextUtils.join(",",
Collections.nCopies(masterKeyIds.length, "?"));
// put together selection string
selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ " IN (" + placeholders + ")";
}
return mKeyRepository.getContentResolver().query(
KeyRings.buildUnifiedKeyRingsUri(), PROJECTION, selection, selectionArgs,
Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
);
}
}

View File

@@ -18,19 +18,17 @@
package org.sufficientlysecure.keychain.operations;
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 android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
@@ -39,7 +37,7 @@ public abstract class BaseOperation<T extends Parcelable> implements PassphraseC
final public Context mContext;
final public Progressable mProgressable;
final public AtomicBoolean mCancelled;
final public CancellationSignal mCancelled;
final public KeyRepository mKeyRepository;
@@ -73,7 +71,7 @@ public abstract class BaseOperation<T extends Parcelable> implements PassphraseC
}
public BaseOperation(Context context, KeyRepository keyRepository,
Progressable progressable, AtomicBoolean cancelled) {
Progressable progressable, CancellationSignal cancelled) {
mContext = context;
mProgressable = progressable;
mKeyRepository = keyRepository;
@@ -102,7 +100,7 @@ public abstract class BaseOperation<T extends Parcelable> implements PassphraseC
}
protected boolean checkCancelled() {
return mCancelled != null && mCancelled.get();
return mCancelled != null && mCancelled.isCanceled();
}
protected void setPreventCancel () {
@@ -113,15 +111,14 @@ public abstract class BaseOperation<T extends Parcelable> implements PassphraseC
@Override
public Passphrase getCachedPassphrase(long subKeyId) throws NoSecretKeyException {
try {
if (subKeyId != key.symmetric) {
long masterKeyId = mKeyRepository.getMasterKeyId(subKeyId);
return getCachedPassphrase(masterKeyId, subKeyId);
if (subKeyId != key.symmetric) {
Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(subKeyId);
if (masterKeyId == null) {
throw new PassphraseCacheInterface.NoSecretKeyException();
}
return getCachedPassphrase(key.symmetric, key.symmetric);
} catch (NotFoundException e) {
throw new PassphraseCacheInterface.NoSecretKeyException();
return getCachedPassphrase(masterKeyId, subKeyId);
}
return getCachedPassphrase(key.symmetric, key.symmetric);
}
@Override

View File

@@ -17,16 +17,16 @@
package org.sufficientlysecure.keychain.operations;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.os.Parcelable;
import android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
abstract class BaseReadWriteOperation<T extends Parcelable> extends BaseOperation<T> {
final KeyWritableRepository mKeyWritableRepository;
public abstract class BaseReadWriteOperation<T extends Parcelable> extends BaseOperation<T> {
protected final KeyWritableRepository mKeyWritableRepository;
BaseReadWriteOperation(Context context,
KeyWritableRepository databaseInteractor,
@@ -36,8 +36,8 @@ abstract class BaseReadWriteOperation<T extends Parcelable> extends BaseOperatio
mKeyWritableRepository = databaseInteractor;
}
BaseReadWriteOperation(Context context, KeyWritableRepository databaseInteractor,
Progressable progressable, AtomicBoolean cancelled) {
protected BaseReadWriteOperation(Context context, KeyWritableRepository databaseInteractor,
Progressable progressable, CancellationSignal cancelled) {
super(context, databaseInteractor, progressable, cancelled);
mKeyWritableRepository = databaseInteractor;

View File

@@ -42,7 +42,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;

View File

@@ -19,10 +19,10 @@ package org.sufficientlysecure.keychain.operations;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@@ -38,10 +38,9 @@ import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.LastUpdateInteractor;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
@@ -62,13 +61,13 @@ import org.sufficientlysecure.keychain.util.Passphrase;
* @see CertifyActionsParcel
*/
public class CertifyOperation extends BaseReadWriteOperation<CertifyActionsParcel> {
private final LastUpdateInteractor lastUpdateInteractor;
private final KeyMetadataDao keyMetadataDao;
public CertifyOperation(Context context, KeyWritableRepository databaseInteractor, Progressable progressable, AtomicBoolean
public CertifyOperation(Context context, KeyWritableRepository keyWritableRepository, Progressable progressable, CancellationSignal
cancelled) {
super(context, databaseInteractor, progressable, cancelled);
super(context, keyWritableRepository, progressable, cancelled);
this.lastUpdateInteractor = LastUpdateInteractor.create(context);
this.keyMetadataDao = KeyMetadataDao.create(context);
}
@NonNull
@@ -85,10 +84,8 @@ public class CertifyOperation extends BaseReadWriteOperation<CertifyActionsParce
log.add(LogType.MSG_CRT_MASTER_FETCH, 1);
CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId);
Passphrase passphrase;
switch (cachedPublicKeyRing.getSecretKeyType(masterKeyId)) {
switch (mKeyRepository.getSecretKeyType(masterKeyId)) {
case PASSPHRASE:
passphrase = cryptoInput.getPassphrase();
if (passphrase == null) {
@@ -234,7 +231,7 @@ public class CertifyOperation extends BaseReadWriteOperation<CertifyActionsParce
log.add(uploadResult, 2);
if (uploadResult.success()) {
lastUpdateInteractor.renewKeyLastUpdatedTime(certifiedKey.getMasterKeyId(), true);
keyMetadataDao.renewKeyLastUpdatedTime(certifiedKey.getMasterKeyId(), true);
uploadOk += 1;
} else {

View File

@@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;

View File

@@ -23,14 +23,13 @@ import java.util.Collections;
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.UpdateTrustResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.DeleteKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;

View File

@@ -19,10 +19,10 @@ package org.sufficientlysecure.keychain.operations;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
@@ -35,9 +35,9 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.LastUpdateInteractor;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
@@ -57,14 +57,14 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
*
*/
public class EditKeyOperation extends BaseReadWriteOperation<SaveKeyringParcel> {
private final LastUpdateInteractor lastUpdateInteractor;
private final KeyMetadataDao keyMetadataDao;
public EditKeyOperation(Context context, KeyWritableRepository databaseInteractor,
Progressable progressable, AtomicBoolean cancelled) {
Progressable progressable, CancellationSignal cancelled) {
super(context, databaseInteractor, progressable, cancelled);
this.lastUpdateInteractor = LastUpdateInteractor.create(context);
this.keyMetadataDao = KeyMetadataDao.create(context);
}
/**
@@ -171,8 +171,8 @@ public class EditKeyOperation extends BaseReadWriteOperation<SaveKeyringParcel>
SaveKeyringResult saveResult = mKeyWritableRepository.saveSecretKeyRing(ring);
log.add(saveResult, 1);
if (isNewKey) {
lastUpdateInteractor.renewKeyLastUpdatedTime(ring.getMasterKeyId(), saveParcel.isShouldUpload());
if (isNewKey || saveParcel.isShouldUpload()) {
keyMetadataDao.renewKeyLastUpdatedTime(ring.getMasterKeyId(), saveParcel.isShouldUpload());
}
// If the save operation didn't succeed, exit here

View File

@@ -29,11 +29,11 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
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 android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.FacebookKeyserverClient;
@@ -54,8 +54,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.LastUpdateInteractor;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -90,7 +90,7 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
public static final String CACHE_FILE_NAME = "key_import.pcl";
private final LastUpdateInteractor lastUpdateInteractor;
private final KeyMetadataDao keyMetadataDao;
private FacebookKeyserverClient facebookServer;
private KeybaseKeyserverClient keybaseServer;
@@ -98,14 +98,14 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
public ImportOperation(Context context, KeyWritableRepository databaseInteractor, Progressable progressable) {
super(context, databaseInteractor, progressable);
this.lastUpdateInteractor = LastUpdateInteractor.create(context);
this.keyMetadataDao = KeyMetadataDao.create(context);
}
public ImportOperation(Context context, KeyWritableRepository databaseInteractor,
Progressable progressable, AtomicBoolean cancelled) {
Progressable progressable, CancellationSignal cancelled) {
super(context, databaseInteractor, progressable, cancelled);
this.lastUpdateInteractor = LastUpdateInteractor.create(context);
this.keyMetadataDao = KeyMetadataDao.create(context);
}
// Overloaded functions for using progressable supplied in constructor during import
@@ -200,7 +200,7 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
byte[] fingerprintHex = entry.getExpectedFingerprint();
if (fingerprintHex != null) {
lastUpdateInteractor.renewKeyLastUpdatedTime(
keyMetadataDao.renewKeyLastUpdatedTime(
KeyFormattingUtils.getKeyIdFromFingerprint(fingerprintHex), false);
}
continue;
@@ -249,8 +249,8 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
importedMasterKeyIds.add(key.getMasterKeyId());
}
if (!skipSave) {
lastUpdateInteractor.renewKeyLastUpdatedTime(key.getMasterKeyId(), keyWasDownloaded);
if (!skipSave && keyWasDownloaded) {
keyMetadataDao.renewKeyLastUpdatedTime(key.getMasterKeyId(), true);
}
}

View File

@@ -50,7 +50,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.InputDataParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;

View File

@@ -0,0 +1,158 @@
package org.sufficientlysecure.keychain.operations;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences;
import timber.log.Timber;
public class KeySyncOperation extends BaseReadWriteOperation<KeySyncParcel> {
// time since last update after which a key should be updated again, in s
private static final long KEY_STALE_THRESHOLD_MILLIS =
Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toMillis(7);
// Time taken by Orbot before a new circuit is created
private static final int ORBOT_CIRCUIT_TIMEOUT_SECONDS =
Constants.DEBUG_KEYSERVER_SYNC ? 2 : (int) TimeUnit.MINUTES.toSeconds(10);
private final KeyMetadataDao keyMetadataDao;
private final Preferences preferences;
public KeySyncOperation(Context context, KeyWritableRepository databaseInteractor,
Progressable progressable, CancellationSignal cancellationSignal) {
super(context, databaseInteractor, progressable, cancellationSignal);
keyMetadataDao = KeyMetadataDao.create(context);
preferences = Preferences.getPreferences(context);
}
@NonNull
@Override
public ImportKeyResult execute(KeySyncParcel input, CryptoInputParcel cryptoInput) {
long staleKeyThreshold = System.currentTimeMillis() - (input.getRefreshAll() ? 0 : KEY_STALE_THRESHOLD_MILLIS);
List<byte[]> staleKeyFingerprints =
keyMetadataDao.getFingerprintsForKeysOlderThan(staleKeyThreshold, TimeUnit.MILLISECONDS);
List<ParcelableKeyRing> staleKeyParcelableKeyRings = fingerprintListToParcelableKeyRings(staleKeyFingerprints);
if (checkCancelled()) { // if we've already been cancelled
return new ImportKeyResult(OperationResult.RESULT_CANCELLED, new OperationResult.OperationLog());
}
// no explicit proxy, retrieve from preferences. Check if we should do a staggered sync
CryptoInputParcel cryptoInputParcel = CryptoInputParcel.createCryptoInputParcel();
ImportKeyResult importKeyResult;
if (preferences.getParcelableProxy().isTorEnabled()) {
importKeyResult = staggeredUpdate(staleKeyParcelableKeyRings, cryptoInputParcel);
} else {
importKeyResult =
directUpdate(staleKeyParcelableKeyRings, cryptoInputParcel);
}
return importKeyResult;
}
private List<ParcelableKeyRing> fingerprintListToParcelableKeyRings(List<byte[]> staleKeyFingerprints) {
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));
}
return result;
}
private ImportKeyResult directUpdate(List<ParcelableKeyRing> keyList,
CryptoInputParcel cryptoInputParcel) {
Timber.d("Starting normal update");
ImportOperation importOp = new ImportOperation(mContext, mKeyWritableRepository, mProgressable, mCancelled);
return importOp.execute(
ImportKeyringParcel.createImportKeyringParcel(keyList, preferences.getPreferredKeyserver()),
cryptoInputParcel
);
}
/**
* will perform a staggered update of user's keys using delays to ensure new Tor circuits, as
* performed by parcimonie. Relevant issue and method at:
* https://github.com/open-keychain/open-keychain/issues/1337
*
* @return result of the sync
*/
private ImportKeyResult staggeredUpdate(List<ParcelableKeyRing> keyList,
CryptoInputParcel cryptoInputParcel) {
Timber.d("Starting staggered update");
// final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
// we are limiting our randomness to ORBOT_CIRCUIT_TIMEOUT_SECONDS for now
final int WEEK_IN_SECONDS = 0;
ImportOperation.KeyImportAccumulator accumulator
= new ImportOperation.KeyImportAccumulator(keyList.size(), null);
// so that the first key can be updated without waiting. This is so that there isn't a
// large gap between a "Start Orbot" notification and the next key update
boolean first = true;
for (ParcelableKeyRing keyRing : keyList) {
int waitTime;
int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size()));
if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT_SECONDS) {
waitTime = staggeredTime;
} else {
waitTime = ORBOT_CIRCUIT_TIMEOUT_SECONDS
+ new Random().nextInt(1 + ORBOT_CIRCUIT_TIMEOUT_SECONDS);
}
if (first) {
waitTime = 0;
first = false;
}
Timber.d("Updating key with a wait time of %d seconds", waitTime);
try {
Thread.sleep(waitTime * 1000);
} catch (InterruptedException e) {
Timber.e(e, "Exception during sleep between key updates");
// skip this one
continue;
}
ArrayList<ParcelableKeyRing> keyWrapper = new ArrayList<>();
keyWrapper.add(keyRing);
if (checkCancelled()) {
return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED,
new OperationResult.OperationLog());
}
ImportKeyResult result =
new ImportOperation(mContext, mKeyWritableRepository, null, mCancelled)
.execute(
ImportKeyringParcel.createImportKeyringParcel(
keyWrapper,
preferences.getPreferredKeyserver()
),
cryptoInputParcel
);
if (result.isPending()) {
return result;
}
accumulator.accumulateKeyImport(result);
}
return accumulator.getConsolidatedResult();
}
}

View File

@@ -0,0 +1,20 @@
package org.sufficientlysecure.keychain.operations;
import android.os.Parcelable;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class KeySyncParcel implements Parcelable {
public abstract boolean getRefreshAll();
public static KeySyncParcel createRefreshAll() {
return new AutoValue_KeySyncParcel(true);
}
public static KeySyncParcel createRefreshOutdated() {
return new AutoValue_KeySyncParcel(false);
}
}

View File

@@ -18,16 +18,29 @@
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 org.json.JSONObject;
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;
@@ -35,28 +48,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
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.network.OkHttpKeybaseClient;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.network.orbot.OrbotHelper;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.List;
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;
public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificationParcel> {
@@ -162,7 +159,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
}
long verifyingKeyId = decryptVerifyResult.getSignatureResult().getKeyId();
byte[] verifyingFingerprint = mKeyRepository.getCachedPublicKeyRing(verifyingKeyId).getFingerprint();
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);

View File

@@ -20,10 +20,10 @@ package org.sufficientlysecure.keychain.operations;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@@ -35,8 +35,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.PromoteKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -50,7 +50,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
*/
public class PromoteKeyOperation extends BaseReadWriteOperation<PromoteKeyringParcel> {
public PromoteKeyOperation(Context context, KeyWritableRepository databaseInteractor,
Progressable progressable, AtomicBoolean cancelled) {
Progressable progressable, CancellationSignal cancelled) {
super(context, databaseInteractor, progressable, cancelled);
}

View File

@@ -19,17 +19,14 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.RevokeResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.RevokeKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -58,19 +55,21 @@ public class RevokeOperation extends BaseReadWriteOperation<RevokeKeyringParcel>
KeyFormattingUtils.beautifyKeyId(masterKeyId));
try {
Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId);
CachedPublicKeyRing keyRing = mKeyRepository.getCachedPublicKeyRing(secretUri);
UnifiedKeyInfo keyInfo = mKeyRepository.getUnifiedKeyInfo(masterKeyId);
if (keyInfo == null) {
log.add(OperationResult.LogType.MSG_REVOKE_ERROR_KEY_FAIL, 1);
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
}
// check if this is a master secret key we can work with
switch (keyRing.getSecretKeyType(masterKeyId)) {
switch (mKeyRepository.getSecretKeyType(masterKeyId)) {
case GNU_DUMMY:
log.add(OperationResult.LogType.MSG_EK_ERROR_DUMMY, 1);
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
}
SaveKeyringParcel.Builder saveKeyringParcel =
SaveKeyringParcel.buildChangeKeyringParcel(masterKeyId, keyRing.getFingerprint());
SaveKeyringParcel.buildChangeKeyringParcel(masterKeyId, keyInfo.fingerprint());
// all revoke operations are made atomic as of now
saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.isShouldUpload(), true,
@@ -96,7 +95,7 @@ public class RevokeOperation extends BaseReadWriteOperation<RevokeKeyringParcel>
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
}
} catch (PgpKeyNotFoundException | KeyWritableRepository.NotFoundException e) {
} catch (KeyWritableRepository.NotFoundException e) {
Timber.e(e, "could not find key to revoke");
log.add(OperationResult.LogType.MSG_REVOKE_ERROR_KEY_FAIL, 1);
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);

View File

@@ -20,22 +20,21 @@ package org.sufficientlysecure.keychain.operations;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.os.CancellationSignal;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
@@ -53,7 +52,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
public SignEncryptOperation(Context context, KeyRepository keyRepository,
Progressable progressable, AtomicBoolean cancelled) {
Progressable progressable, CancellationSignal cancelled) {
super(context, keyRepository, progressable, cancelled);
}

View File

@@ -18,15 +18,24 @@
package org.sufficientlysecure.keychain.operations;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.Proxy;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.os.CancellationSignal;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient;
import org.sufficientlysecure.keychain.keyimport.KeyserverClient.AddKeyException;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
import org.sufficientlysecure.keychain.network.orbot.OrbotHelper;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.UploadResult;
@@ -35,31 +44,26 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
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.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.network.orbot.OrbotHelper;
import timber.log.Timber;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.Proxy;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* An operation class which implements the upload of a single key to a key server.
*/
public class UploadOperation extends BaseOperation<UploadKeyringParcel> {
private KeyMetadataDao keyMetadataDao;
public UploadOperation(Context context, KeyRepository keyRepository,
Progressable progressable, AtomicBoolean cancelled) {
Progressable progressable, CancellationSignal cancelled) {
super(context, keyRepository, progressable, cancelled);
keyMetadataDao = KeyMetadataDao.create(mContext);
}
@NonNull
@@ -150,11 +154,18 @@ public class UploadOperation extends BaseOperation<UploadKeyringParcel> {
keyring.encode(aos);
aos.close();
if (checkCancelled()) {
log.add(LogType.MSG_OPERATION_CANCELLED, 0);
return new UploadResult(UploadResult.RESULT_CANCELLED, log);
}
String armoredKey = bos.toString("UTF-8");
keyserverInteractor.add(armoredKey, proxy);
updateProgress(R.string.progress_uploading, 1, 1);
keyMetadataDao.renewKeyLastUpdatedTime(keyring.getMasterKeyId(), true);
log.add(LogType.MSG_UPLOAD_SUCCESS, 1);
return new UploadResult(UploadResult.RESULT_OK, log);
} catch (IOException e) {

View File

@@ -419,7 +419,6 @@ public abstract class OperationResult implements Parcelable {
// import secret
MSG_IS(LogLevel.START, R.string.msg_is),
MSG_IS_BAD_TYPE_PUBLIC (LogLevel.WARN, R.string.msg_is_bad_type_public),
MSG_IS_DB_EXCEPTION (LogLevel.DEBUG, R.string.msg_is_db_exception),
MSG_IS_ERROR_IO_EXC(LogLevel.DEBUG, R.string.msg_is_error_io_exc),
MSG_IS_MERGE_PUBLIC (LogLevel.DEBUG, R.string.msg_is_merge_public),
MSG_IS_MERGE_SECRET (LogLevel.DEBUG, R.string.msg_is_merge_secret),

View File

@@ -43,9 +43,9 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
*/
public abstract class CanonicalizedKeyRing extends KeyRing {
private final int mVerified;
private final VerificationStatus mVerified;
CanonicalizedKeyRing(int verified) {
CanonicalizedKeyRing(VerificationStatus verified) {
mVerified = verified;
}
@@ -53,7 +53,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getRing().getPublicKey().getKeyID();
}
public int getVerified() {
public VerificationStatus getVerified() {
return mVerified;
}
@@ -61,15 +61,11 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getRing().getPublicKey().getFingerprint();
}
public byte[] getRawPrimaryUserId() throws PgpKeyNotFoundException {
public byte[] getRawPrimaryUserId() {
return getPublicKey().getRawPrimaryUserId();
}
public String getPrimaryUserId() throws PgpKeyNotFoundException {
return getPublicKey().getPrimaryUserId();
}
public String getPrimaryUserIdWithFallback() throws PgpKeyNotFoundException {
public String getPrimaryUserIdWithFallback() {
return getPublicKey().getPrimaryUserIdWithFallback();
}
@@ -107,10 +103,6 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return creationDate.after(now) || (expirationDate != null && expirationDate.before(now));
}
public boolean canCertify() throws PgpKeyNotFoundException {
return getRing().getPublicKey().isEncryptionKey();
}
public Set<Long> getEncryptIds() {
HashSet<Long> result = new HashSet<>();
for (CanonicalizedPublicKey key : publicKeyIterator()) {
@@ -130,15 +122,6 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
throw new PgpKeyNotFoundException("No valid encryption key found!");
}
public boolean hasEncrypt() throws PgpKeyNotFoundException {
try {
getEncryptId();
return true;
} catch (PgpKeyNotFoundException e) {
return false;
}
}
public long getSigningId() throws PgpKeyNotFoundException {
for(CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canSign() && key.isValid()) {
@@ -194,4 +177,8 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return false;
}
public enum VerificationStatus {
UNVERIFIED, VERIFIED_SELF, VERIFIED_SECRET
}
}

View File

@@ -41,12 +41,12 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
private PGPPublicKeyRing mRing;
CanonicalizedPublicKeyRing(PGPPublicKeyRing ring, int verified) {
CanonicalizedPublicKeyRing(PGPPublicKeyRing ring, VerificationStatus verified) {
super(verified);
mRing = ring;
}
public CanonicalizedPublicKeyRing(byte[] blob, int verified) {
public CanonicalizedPublicKeyRing(byte[] blob, VerificationStatus verified) {
super(verified);
if(mRing == null) {
// get first object in block
@@ -100,7 +100,7 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
* - the user id that matches the userIdToKeep parameter, or the primary user id if none matches
* each with their most recent binding certificates
*/
public CanonicalizedPublicKeyRing minimize(@Nullable String userIdToKeep) throws IOException, PgpKeyNotFoundException {
public CanonicalizedPublicKeyRing minimize(@Nullable String userIdToKeep) throws IOException {
CanonicalizedPublicKey masterKey = getPublicKey();
PGPPublicKey masterPubKey = masterKey.getPublicKey();
boolean userIdStrippedOk = false;

View File

@@ -47,8 +47,7 @@ import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.SessionKeySecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
import timber.log.Timber;
@@ -326,7 +325,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
spGen.setSignatureCreationTime(false, creationTimestamp);
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
} catch (PgpKeyNotFoundException | PGPException e) {
} catch (PGPException e) {
// TODO: simply throw PGPException!
throw new PgpGeneralException("Error initializing signature!", e);
}

View File

@@ -35,12 +35,12 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
private PGPSecretKeyRing mRing;
CanonicalizedSecretKeyRing(PGPSecretKeyRing ring, int verified) {
CanonicalizedSecretKeyRing(PGPSecretKeyRing ring, VerificationStatus verified) {
super(verified);
mRing = ring;
}
public CanonicalizedSecretKeyRing(byte[] blob, int verified)
public CanonicalizedSecretKeyRing(byte[] blob, VerificationStatus verified)
{
super(verified);
JcaPGPObjectFactory factory = new JcaPGPObjectFactory(blob);

View File

@@ -17,16 +17,12 @@
package org.sufficientlysecure.keychain.pgp;
import android.text.TextUtils;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.openintents.openpgp.util.OpenPgpUtils.UserId;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* An abstract KeyRing.
* <p/>
@@ -36,29 +32,18 @@ import java.util.regex.Pattern;
* here.
*
* @see CanonicalizedKeyRing
* @see org.sufficientlysecure.keychain.provider.CachedPublicKeyRing
*/
public abstract class KeyRing {
abstract public long getMasterKeyId() throws PgpKeyNotFoundException;
abstract public String getPrimaryUserId() throws PgpKeyNotFoundException;
abstract public String getPrimaryUserIdWithFallback() throws PgpKeyNotFoundException;
public UserId getSplitPrimaryUserIdWithFallback() throws PgpKeyNotFoundException {
return splitUserId(getPrimaryUserIdWithFallback());
}
abstract public boolean isRevoked() throws PgpKeyNotFoundException;
abstract public boolean canCertify() throws PgpKeyNotFoundException;
abstract public long getEncryptId() throws PgpKeyNotFoundException;
abstract public boolean hasEncrypt() throws PgpKeyNotFoundException;
abstract public int getVerified() throws PgpKeyNotFoundException;
abstract public VerificationStatus getVerified() throws PgpKeyNotFoundException;
/**
* Splits userId string into naming part, email part, and comment part

View File

@@ -20,14 +20,14 @@ package org.sufficientlysecure.keychain.pgp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.openintents.openpgp.util.OpenPgpUtils.UserId;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import timber.log.Timber;
@@ -41,8 +41,8 @@ public class OpenPgpSignatureResultBuilder {
// OpenPgpSignatureResult
private String mPrimaryUserId;
private ArrayList<String> mUserIds = new ArrayList<>();
private ArrayList<String> mConfirmedUserIds;
private List<String> mUserIds = new ArrayList<>();
private List<String> mConfirmedUserIds;
private long mKeyId;
private SenderStatusResult mSenderStatusResult;
@@ -101,7 +101,7 @@ public class OpenPgpSignatureResultBuilder {
this.mIsKeyExpired = keyExpired;
}
public void setUserIds(ArrayList<String> userIds, ArrayList<String> confirmedUserIds) {
public void setUserIds(List<String> userIds, List<String> confirmedUserIds) {
this.mUserIds = userIds;
this.mConfirmedUserIds = confirmedUserIds;
}
@@ -118,20 +118,11 @@ public class OpenPgpSignatureResultBuilder {
// from RING
setKeyId(signingRing.getMasterKeyId());
try {
setPrimaryUserId(signingRing.getPrimaryUserIdWithFallback());
} catch (PgpKeyNotFoundException e) {
Timber.d("No primary user id in keyring with master key id " + signingRing.getMasterKeyId());
}
setSignatureKeyCertified(signingRing.getVerified() > 0);
setPrimaryUserId(signingRing.getPrimaryUserIdWithFallback());
setSignatureKeyCertified(signingRing.getVerified() == VerificationStatus.VERIFIED_SECRET);
ArrayList<String> allUserIds = signingRing.getUnorderedUserIds();
ArrayList<String> confirmedUserIds;
try {
confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId());
} catch (NotFoundException e) {
throw new IllegalStateException("Key didn't exist anymore for user id query!", e);
}
List<String> allUserIds = signingRing.getUnorderedUserIds();
List<String> confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId());
setUserIds(allUserIds, confirmedUserIds);
mSenderStatusResult = processSenderStatusResult(allUserIds, confirmedUserIds);
@@ -142,7 +133,7 @@ public class OpenPgpSignatureResultBuilder {
}
private SenderStatusResult processSenderStatusResult(
ArrayList<String> allUserIds, ArrayList<String> confirmedUserIds) {
List<String> allUserIds, List<String> confirmedUserIds) {
if (mSenderAddress == null) {
return SenderStatusResult.UNKNOWN;
}
@@ -156,7 +147,7 @@ public class OpenPgpSignatureResultBuilder {
}
}
private static boolean userIdListContainsAddress(String senderAddress, ArrayList<String> confirmedUserIds) {
private static boolean userIdListContainsAddress(String senderAddress, List<String> confirmedUserIds) {
for (String rawUserId : confirmedUserIds) {
UserId userId = OpenPgpUtils.splitUserId(rawUserId);
if (senderAddress.equalsIgnoreCase(userId.email)) {

View File

@@ -71,11 +71,8 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmPr
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequireAnyDecryptPassphraseBuilder;
@@ -635,13 +632,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
break;
}
CachedPublicKeyRing cachedPublicKeyRing;
try {
// get actual keyring object based on master key id
cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)
);
long masterKeyId = cachedPublicKeyRing.getMasterKeyId();
Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(subKeyId);
if (masterKeyId == null) {
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
continue;
}
// allow only specific keys for decryption?
if (input.getAllowedKeyIds() != null) {
@@ -658,7 +655,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
}
SecretKeyType secretKeyType = cachedPublicKeyRing.getSecretKeyType(subKeyId);
SecretKeyType secretKeyType = mKeyRepository.getSecretKeyType(subKeyId);
if (!secretKeyType.isUsable()) {
decryptionKey = null;
log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, indent + 1);
@@ -713,7 +710,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
encryptedDataAsymmetric = encData;
decryptionKey = candidateDecryptionKey;
} catch (PgpKeyNotFoundException | KeyWritableRepository.NotFoundException e) {
} catch (KeyWritableRepository.NotFoundException e) {
// continue with the next packet in the while loop
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
continue;

View File

@@ -33,7 +33,8 @@ import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean;
import android.support.v4.os.CancellationSignal;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
@@ -108,7 +109,7 @@ import timber.log.Timber;
public class PgpKeyOperation {
private Stack<Progressable> mProgress;
private AtomicBoolean mCancelled;
private CancellationSignal mCancelled;
public PgpKeyOperation(Progressable progress) {
super();
@@ -118,13 +119,13 @@ public class PgpKeyOperation {
}
}
public PgpKeyOperation(Progressable progress, AtomicBoolean cancelled) {
public PgpKeyOperation(Progressable progress, CancellationSignal cancelled) {
this(progress);
mCancelled = cancelled;
}
private boolean checkCancelled() {
return mCancelled != null && mCancelled.get();
return mCancelled != null && mCancelled.isCanceled();
}
private void subProgressPush(int from, int to) {

View File

@@ -33,11 +33,11 @@ import java.security.SignatureException;
import java.util.Collection;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.os.CancellationSignal;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
@@ -63,10 +63,9 @@ import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainComp
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainHashAlgorithmTags;
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -105,7 +104,7 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa
}
}
public PgpSignEncryptOperation(Context context, KeyRepository keyRepository, Progressable progressable, AtomicBoolean cancelled) {
public PgpSignEncryptOperation(Context context, KeyRepository keyRepository, Progressable progressable, CancellationSignal cancelled) {
super(context, keyRepository, progressable, cancelled);
}
@@ -226,8 +225,8 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa
Long signingSubKeyId = data.getSignatureSubKeyId();
if (signingSubKeyId == null) {
try {
signingSubKeyId = mKeyRepository.getCachedPublicKeyRing(signingMasterKeyId).getSecretSignId();
} catch (PgpKeyNotFoundException e) {
signingSubKeyId = mKeyRepository.getSecretSignId(signingMasterKeyId);
} catch (NotFoundException e) {
log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
@@ -257,7 +256,7 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
switch (mKeyRepository.getCachedPublicKeyRing(signingMasterKeyId).getSecretKeyType(signingSubKeyId)) {
switch (mKeyRepository.getSecretKeyType(signingSubKeyId)) {
case DIVERT_TO_CARD:
case PASSPHRASE_EMPTY: {
if (!signingKey.unlock(new Passphrase())) {
@@ -651,8 +650,7 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa
private boolean processEncryptionMasterKeyId(int indent, OperationLog log, PgpSignEncryptData data,
PGPEncryptedDataGenerator cPk, long encryptMasterKeyId) {
try {
CanonicalizedPublicKeyRing keyRing = mKeyRepository.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(encryptMasterKeyId));
CanonicalizedPublicKeyRing keyRing = mKeyRepository.getCanonicalizedPublicKeyRing(encryptMasterKeyId);
Set<Long> encryptSubKeyIds = keyRing.getEncryptIds();
for (Long subKeyId : encryptSubKeyIds) {
CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId);

View File

@@ -38,9 +38,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem.DecryptVerifySecurityProblemBuilder;
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureSigningAlgorithm;
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import timber.log.Timber;
@@ -160,9 +159,11 @@ class PgpSignatureChecker {
for (int i = 0; i < sigList.size(); ++i) {
try {
long sigKeyId = sigList.get(i).getKeyID();
CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
);
Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(sigKeyId);
if (masterKeyId == null) {
continue;
}
CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId);
CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId);
if ( ! keyCandidate.canSign()) {
continue;
@@ -183,9 +184,11 @@ class PgpSignatureChecker {
for (int i = 0; i < sigList.size(); ++i) {
try {
long sigKeyId = sigList.get(i).getKeyID();
CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
);
Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(sigKeyId);
if (masterKeyId == null) {
continue;
}
CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId);
CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId);
if ( ! keyCandidate.canSign()) {
continue;

View File

@@ -64,6 +64,7 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -1115,8 +1116,8 @@ public class UncachedKeyRing {
log.add(LogType.MSG_KC_SUCCESS, indent);
}
return isSecret() ? new CanonicalizedSecretKeyRing((PGPSecretKeyRing) ring, 1)
: new CanonicalizedPublicKeyRing((PGPPublicKeyRing) ring, 0);
return isSecret() ? new CanonicalizedSecretKeyRing((PGPSecretKeyRing) ring, VerificationStatus.VERIFIED_SECRET)
: new CanonicalizedPublicKeyRing((PGPPublicKeyRing) ring, VerificationStatus.UNVERIFIED);
}
/** This operation merges information from a different keyring, returning a combined

View File

@@ -1,198 +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.provider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.remote.AppSettings;
public class ApiDataAccessObject {
private final SimpleContentResolverInterface mQueryInterface;
public ApiDataAccessObject(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
mQueryInterface = new SimpleContentResolverInterface() {
@Override
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
}
@Override
public Uri insert(Uri contentUri, ContentValues values) {
return contentResolver.insert(contentUri, values);
}
@Override
public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) {
return contentResolver.update(contentUri, values, where, selectionArgs);
}
@Override
public int delete(Uri contentUri, String where, String[] selectionArgs) {
return contentResolver.delete(contentUri, where, selectionArgs);
}
};
}
public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) {
mQueryInterface = queryInterface;
}
public ArrayList<String> getRegisteredApiApps() {
Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null);
ArrayList<String> packageNames = new ArrayList<>();
try {
if (cursor != null) {
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
if (cursor.moveToFirst()) {
do {
packageNames.add(cursor.getString(packageNameCol));
} while (cursor.moveToNext());
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return packageNames;
}
private ContentValues contentValueForApiApps(AppSettings appSettings) {
ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate());
return values;
}
public void insertApiApp(AppSettings appSettings) {
mQueryInterface.insert(ApiApps.CONTENT_URI, contentValueForApiApps(appSettings));
}
public void deleteApiApp(String packageName) {
mQueryInterface.delete(ApiApps.buildByPackageNameUri(packageName), null, null);
}
/**
* Must be an uri pointing to an account
*/
public AppSettings getApiAppSettings(Uri uri) {
AppSettings settings = null;
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
settings = new AppSettings();
settings.setPackageName(cursor.getString(
cursor.getColumnIndex(ApiApps.PACKAGE_NAME)));
settings.setPackageCertificate(cursor.getBlob(
cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE)));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return settings;
}
public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
HashSet<Long> keyIds = new HashSet<>();
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
try {
if (cursor != null) {
int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID);
while (cursor.moveToNext()) {
keyIds.add(cursor.getLong(keyIdColumn));
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return keyIds;
}
public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds)
throws RemoteException, OperationApplicationException {
// wipe whole table of allowed keys for this account
mQueryInterface.delete(uri, null, null);
// re-insert allowed key ids
for (Long keyId : allowedKeyIds) {
ContentValues values = new ContentValues();
values.put(ApiAllowedKeys.KEY_ID, keyId);
mQueryInterface.insert(uri, values);
}
}
public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) {
ContentValues values = new ContentValues();
values.put(ApiAllowedKeys.KEY_ID, allowedKeyId);
mQueryInterface.insert(uri, values);
}
public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) {
Uri uri = ApiAllowedKeys.buildBaseUri(packageName);
addAllowedKeyIdForApp(uri, allowedKeyId);
}
public byte[] getApiAppCertificate(String packageName) {
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE};
Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null);
try {
byte[] signature = null;
if (cursor != null && cursor.moveToFirst()) {
int signatureCol = 0;
signature = cursor.getBlob(signatureCol);
}
return signature;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}

View File

@@ -1,328 +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.provider;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.format.DateUtils;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
public class AutocryptPeerDataAccessObject {
private static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS;
private static final String[] PROJECTION_LAST_SEEN = { ApiAutocryptPeer.LAST_SEEN };
private static final String[] PROJECTION_LAST_SEEN_KEY = { ApiAutocryptPeer.LAST_SEEN_KEY };
private static final String[] PROJECTION_GOSSIP_LAST_SEEN_KEY = { ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY };
private static final String[] PROJECTION_MASTER_KEY_ID = { ApiAutocryptPeer.MASTER_KEY_ID };
private static final String[] PROJECTION_AUTOCRYPT_QUERY = {
ApiAutocryptPeer.IDENTIFIER,
ApiAutocryptPeer.LAST_SEEN,
ApiAutocryptPeer.MASTER_KEY_ID,
ApiAutocryptPeer.LAST_SEEN_KEY,
ApiAutocryptPeer.IS_MUTUAL,
ApiAutocryptPeer.KEY_IS_REVOKED,
ApiAutocryptPeer.KEY_IS_EXPIRED,
ApiAutocryptPeer.KEY_IS_VERIFIED,
ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID,
ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY,
ApiAutocryptPeer.GOSSIP_KEY_IS_REVOKED,
ApiAutocryptPeer.GOSSIP_KEY_IS_EXPIRED,
ApiAutocryptPeer.GOSSIP_KEY_IS_VERIFIED,
};
private static final int INDEX_IDENTIFIER = 0;
private static final int INDEX_LAST_SEEN = 1;
private static final int INDEX_MASTER_KEY_ID = 2;
private static final int INDEX_LAST_SEEN_KEY = 3;
private static final int INDEX_STATE = 4;
private static final int INDEX_KEY_IS_REVOKED = 5;
private static final int INDEX_KEY_IS_EXPIRED = 6;
private static final int INDEX_KEY_IS_VERIFIED = 7;
private static final int INDEX_GOSSIP_MASTER_KEY_ID = 8;
private static final int INDEX_GOSSIP_LAST_SEEN_KEY = 9;
private static final int INDEX_GOSSIP_KEY_IS_REVOKED = 10;
private static final int INDEX_GOSSIP_KEY_IS_EXPIRED = 11;
private static final int INDEX_GOSSIP_KEY_IS_VERIFIED = 12;
private final SimpleContentResolverInterface queryInterface;
private final String packageName;
private final DatabaseNotifyManager databaseNotifyManager;
public AutocryptPeerDataAccessObject(Context context, String packageName) {
this.packageName = packageName;
this.databaseNotifyManager = DatabaseNotifyManager.create(context);
final ContentResolver contentResolver = context.getContentResolver();
queryInterface = new SimpleContentResolverInterface() {
@Override
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
}
@Override
public Uri insert(Uri contentUri, ContentValues values) {
return contentResolver.insert(contentUri, values);
}
@Override
public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) {
return contentResolver.update(contentUri, values, where, selectionArgs);
}
@Override
public int delete(Uri contentUri, String where, String[] selectionArgs) {
return contentResolver.delete(contentUri, where, selectionArgs);
}
};
}
public AutocryptPeerDataAccessObject(SimpleContentResolverInterface queryInterface, String packageName,
DatabaseNotifyManager databaseNotifyManager) {
this.queryInterface = queryInterface;
this.packageName = packageName;
this.databaseNotifyManager = databaseNotifyManager;
}
public Long getMasterKeyIdForAutocryptPeer(String autocryptId) {
Cursor cursor = queryInterface.query(
ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
PROJECTION_MASTER_KEY_ID, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public Date getLastSeen(String autocryptId) {
Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
PROJECTION_LAST_SEEN, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long lastUpdated = cursor.getLong(0);
return new Date(lastUpdated);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public Date getLastSeenKey(String autocryptId) {
Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
PROJECTION_LAST_SEEN_KEY, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long lastUpdated = cursor.getLong(0);
return new Date(lastUpdated);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public Date getLastSeenGossip(String autocryptId) {
Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
PROJECTION_GOSSIP_LAST_SEEN_KEY, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long lastUpdated = cursor.getLong(0);
return new Date(lastUpdated);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public void updateLastSeen(String autocryptId, Date date) {
ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime());
queryInterface
.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
}
public void updateKey(String autocryptId, Date effectiveDate, long masterKeyId, boolean isMutual) {
ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
cv.put(ApiAutocryptPeer.LAST_SEEN_KEY, effectiveDate.getTime());
cv.put(ApiAutocryptPeer.IS_MUTUAL, isMutual ? 1 : 0);
queryInterface
.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId);
}
public void updateKeyGossipFromAutocrypt(String autocryptId, Date effectiveDate, long masterKeyId) {
updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_AUTOCRYPT);
}
public void updateKeyGossipFromSignature(String autocryptId, Date effectiveDate, long masterKeyId) {
updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_SIGNATURE);
}
public void updateKeyGossipFromDedup(String autocryptId, Date effectiveDate, long masterKeyId) {
updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_DEDUP);
}
private void updateKeyGossip(String autocryptId, Date effectiveDate, long masterKeyId, int origin) {
ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, masterKeyId);
cv.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, effectiveDate.getTime());
cv.put(ApiAutocryptPeer.GOSSIP_ORIGIN, origin);
queryInterface
.update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null);
databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId);
}
public void delete(String autocryptId) {
Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId);
queryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null);
databaseNotifyManager.notifyAutocryptDelete(autocryptId, masterKeyId);
}
public List<AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) {
List<AutocryptRecommendationResult> result = new ArrayList<>(autocryptIds.length);
Cursor cursor = queryAutocryptPeerData(autocryptIds);
try {
while (cursor.moveToNext()) {
AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(cursor);
result.add(peerResult);
}
} finally {
cursor.close();
}
return result;
}
/** Determines Autocrypt "ui-recommendation", according to spec.
* See https://autocrypt.org/level1.html#recommendations-for-single-recipient-messages
*/
private AutocryptRecommendationResult determineAutocryptRecommendation(Cursor cursor) {
String peerId = cursor.getString(INDEX_IDENTIFIER);
AutocryptRecommendationResult keyRecommendation = determineAutocryptKeyRecommendation(peerId, cursor);
if (keyRecommendation != null) return keyRecommendation;
AutocryptRecommendationResult gossipRecommendation = determineAutocryptGossipRecommendation(peerId, cursor);
if (gossipRecommendation != null) return gossipRecommendation;
return new AutocryptRecommendationResult(peerId, AutocryptState.DISABLE, null, false);
}
@Nullable
private AutocryptRecommendationResult determineAutocryptKeyRecommendation(String peerId, Cursor cursor) {
boolean hasKey = !cursor.isNull(INDEX_MASTER_KEY_ID);
boolean isRevoked = cursor.getInt(INDEX_KEY_IS_REVOKED) != 0;
boolean isExpired = cursor.getInt(INDEX_KEY_IS_EXPIRED) != 0;
if (!hasKey || isRevoked || isExpired) {
return null;
}
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
long lastSeen = cursor.getLong(INDEX_LAST_SEEN);
long lastSeenKey = cursor.getLong(INDEX_LAST_SEEN_KEY);
boolean isVerified = cursor.getInt(INDEX_KEY_IS_VERIFIED) != 0;
if (lastSeenKey < (lastSeen - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS)) {
return new AutocryptRecommendationResult(peerId, AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified);
}
boolean isMutual = cursor.getInt(INDEX_STATE) != 0;
if (isMutual) {
return new AutocryptRecommendationResult(peerId, AutocryptState.MUTUAL, masterKeyId, isVerified);
} else {
return new AutocryptRecommendationResult(peerId, AutocryptState.AVAILABLE, masterKeyId, isVerified);
}
}
@Nullable
private AutocryptRecommendationResult determineAutocryptGossipRecommendation(String peerId, Cursor cursor) {
boolean gossipHasKey = !cursor.isNull(INDEX_GOSSIP_MASTER_KEY_ID);
boolean gossipIsRevoked = cursor.getInt(INDEX_GOSSIP_KEY_IS_REVOKED) != 0;
boolean gossipIsExpired = cursor.getInt(INDEX_GOSSIP_KEY_IS_EXPIRED) != 0;
boolean isVerified = cursor.getInt(INDEX_GOSSIP_KEY_IS_VERIFIED) != 0;
if (!gossipHasKey || gossipIsRevoked || gossipIsExpired) {
return null;
}
long masterKeyId = cursor.getLong(INDEX_GOSSIP_MASTER_KEY_ID);
return new AutocryptRecommendationResult(peerId, AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified);
}
private Cursor queryAutocryptPeerData(String[] autocryptIds) {
StringBuilder selection = new StringBuilder(ApiAutocryptPeer.IDENTIFIER + " IN (?");
for (int i = 1; i < autocryptIds.length; i++) {
selection.append(",?");
}
selection.append(")");
return queryInterface.query(ApiAutocryptPeer.buildByPackageName(packageName),
PROJECTION_AUTOCRYPT_QUERY, selection.toString(), autocryptIds, null);
}
public static class AutocryptRecommendationResult {
public final String peerId;
public final Long masterKeyId;
public final AutocryptState autocryptState;
public final boolean isVerified;
AutocryptRecommendationResult(String peerId, AutocryptState autocryptState, Long masterKeyId,
boolean isVerified) {
this.peerId = peerId;
this.autocryptState = autocryptState;
this.masterKeyId = masterKeyId;
this.isVerified = isVerified;
}
}
public enum AutocryptState {
DISABLE, DISCOURAGED_OLD, DISCOURAGED_GOSSIP, AVAILABLE, MUTUAL
}
}

View File

@@ -1,287 +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.provider;
import android.net.Uri;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import timber.log.Timber;
/** This implementation of KeyRing provides a cached view of PublicKeyRing
* objects based on database queries exclusively.
*
* This class should be used where only few points of data but no actual
* cryptographic operations are required about a PublicKeyRing which is already
* in the database. This happens commonly in UI code, where parsing of a PGP
* key for examination would be a very expensive operation.
*
* Each getter method is implemented using a more or less expensive database
* query, while object construction is (almost) free. A common pattern is
* mProviderHelper.getCachedKeyRing(uri).getterMethod()
*
* TODO Ensure that the values returned here always match the ones returned by
* the parsed KeyRing!
*
*/
public class CachedPublicKeyRing extends KeyRing {
final KeyRepository mKeyRepository;
final Uri mUri;
public CachedPublicKeyRing(KeyRepository keyRepository, Uri uri) {
mKeyRepository = keyRepository;
mUri = uri;
}
@Override
public long getMasterKeyId() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.MASTER_KEY_ID, KeyRepository.FIELD_TYPE_INTEGER);
return (Long) data;
} catch (KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
/**
* Find the master key id related to a given query. The id will either be extracted from the
* query, which should work for all specific /key_rings/ queries, or will be queried if it can't.
*/
public long extractOrGetMasterKeyId() throws PgpKeyNotFoundException {
// try extracting from the uri first
String firstSegment = mUri.getPathSegments().get(1);
if (!"find".equals(firstSegment)) try {
return Long.parseLong(firstSegment);
} catch (NumberFormatException e) {
// didn't work? oh well.
Timber.d("Couldn't get masterKeyId from URI, querying...");
}
return getMasterKeyId();
}
public byte[] getFingerprint() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.FINGERPRINT, KeyRepository.FIELD_TYPE_BLOB);
return (byte[]) data;
} catch (KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public long getCreationTime() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.CREATION, KeyRepository.FIELD_TYPE_INTEGER);
return (long) data;
} catch (KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
@Override
public String getPrimaryUserId() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.USER_ID,
KeyRepository.FIELD_TYPE_STRING);
return (String) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public String getPrimaryUserIdWithFallback() throws PgpKeyNotFoundException {
return getPrimaryUserId();
}
public String getName() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeyRings.NAME,
KeyRepository.FIELD_TYPE_STRING);
return (String) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public String getEmail() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeyRings.EMAIL,
KeyRepository.FIELD_TYPE_STRING);
return (String) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public String getComment() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeyRings.COMMENT,
KeyRepository.FIELD_TYPE_STRING);
return (String) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
@Override
public boolean isRevoked() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.IS_REVOKED,
KeyRepository.FIELD_TYPE_INTEGER);
return (Long) data > 0;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
@Override
public boolean canCertify() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.HAS_CERTIFY_SECRET,
KeyRepository.FIELD_TYPE_NULL);
return !((Boolean) data);
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
@Override
public long getEncryptId() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeyRings.HAS_ENCRYPT,
KeyRepository.FIELD_TYPE_INTEGER);
return (Long) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
@Override
public boolean hasEncrypt() throws PgpKeyNotFoundException {
return getEncryptId() != 0;
}
/** Returns the key id which should be used for signing.
*
* This method returns keys which are actually available (ie. secret available, and not stripped,
* revoked, or expired), hence only works on keyrings where a secret key is available!
*
*/
public long getSecretSignId() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeyRings.HAS_SIGN_SECRET,
KeyRepository.FIELD_TYPE_INTEGER);
return (Long) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
/** Returns the key id which should be used for authentication.
*
* This method returns keys which are actually available (ie. secret available, and not stripped,
* revoked, or expired), hence only works on keyrings where a secret key is available!
*
*/
public long getSecretAuthenticationId() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeyRings.HAS_AUTHENTICATE_SECRET,
KeyRepository.FIELD_TYPE_INTEGER);
return (Long) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public boolean hasSecretAuthentication() throws PgpKeyNotFoundException {
return getSecretAuthenticationId() != 0;
}
public long getAuthenticationId() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeyRings.HAS_AUTHENTICATE,
KeyRepository.FIELD_TYPE_INTEGER);
return (Long) data;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public boolean hasAuthentication() throws PgpKeyNotFoundException {
return getAuthenticationId() != 0;
}
@Override
public int getVerified() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.VERIFIED,
KeyRepository.FIELD_TYPE_INTEGER);
return ((Long) data).intValue();
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public boolean hasAnySecret() throws PgpKeyNotFoundException {
try {
Object data = mKeyRepository.getGenericData(mUri,
KeychainContract.KeyRings.HAS_ANY_SECRET,
KeyRepository.FIELD_TYPE_INTEGER);
return (Long) data > 0;
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException {
Object data = mKeyRepository.getGenericData(Keys.buildKeysUri(mUri),
KeyRings.HAS_SECRET,
KeyRepository.FIELD_TYPE_INTEGER,
KeyRings.KEY_ID + " = " + Long.toString(keyId));
return SecretKeyType.fromNum(((Long) data).intValue());
}
public byte[] getEncoded() throws PgpKeyNotFoundException {
try {
return mKeyRepository.loadPublicKeyRingData(getMasterKeyId());
} catch(KeyWritableRepository.NotFoundException e) {
throw new PgpKeyNotFoundException(e);
}
}
}

View File

@@ -1,42 +0,0 @@
package org.sufficientlysecure.keychain.provider;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
public class DatabaseNotifyManager {
private ContentResolver contentResolver;
public static DatabaseNotifyManager create(Context context) {
ContentResolver contentResolver = context.getContentResolver();
return new DatabaseNotifyManager(contentResolver);
}
private DatabaseNotifyManager(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
}
public void notifyKeyChange(long masterKeyId) {
Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId);
contentResolver.notifyChange(uri, null);
}
public void notifyAutocryptDelete(String autocryptId, Long masterKeyId) {
Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId);
contentResolver.notifyChange(uri, null);
}
public void notifyAutocryptUpdate(String autocryptId, long masterKeyId) {
Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId);
contentResolver.notifyChange(uri, null);
}
public void notifyKeyserverStatusChange(long masterKeyId) {
Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId);
contentResolver.notifyChange(uri, null);
}
}

View File

@@ -1,346 +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.provider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import timber.log.Timber;
public class KeyRepository {
// If we ever switch to api level 11, we can ditch this whole mess!
public static final int FIELD_TYPE_NULL = 1;
// this is called integer to stay coherent with the constants in Cursor (api level 11)
public static final int FIELD_TYPE_INTEGER = 2;
public static final int FIELD_TYPE_FLOAT = 3;
public static final int FIELD_TYPE_STRING = 4;
public static final int FIELD_TYPE_BLOB = 5;
final ContentResolver contentResolver;
final LocalPublicKeyStorage mLocalPublicKeyStorage;
OperationLog mLog;
int mIndent;
public static KeyRepository create(Context context) {
ContentResolver contentResolver = context.getContentResolver();
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
return new KeyRepository(contentResolver, localPublicKeyStorage);
}
private KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage) {
this(contentResolver, localPublicKeyStorage, new OperationLog(), 0);
}
KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage,
OperationLog log, int indent) {
this.contentResolver = contentResolver;
mLocalPublicKeyStorage = localPublicKeyStorage;
mIndent = indent;
mLog = log;
}
public OperationLog getLog() {
return mLog;
}
public void log(LogType type) {
if (mLog != null) {
mLog.add(type, mIndent);
}
}
public void log(LogType type, Object... parameters) {
if (mLog != null) {
mLog.add(type, mIndent, parameters);
}
}
public void clearLog() {
mLog = new OperationLog();
}
Object getGenericData(Uri uri, String column, int type) throws NotFoundException {
Object result = getGenericData(uri, new String[]{column}, new int[]{type}, null).get(column);
if (result == null) {
throw new NotFoundException();
}
return result;
}
Object getGenericDataOrNull(Uri uri, String column, int type) throws NotFoundException {
return getGenericData(uri, new String[]{column}, new int[]{type}, null).get(column);
}
Object getGenericData(Uri uri, String column, int type, String selection)
throws NotFoundException {
return getGenericData(uri, new String[]{column}, new int[]{type}, selection).get(column);
}
private HashMap<String, Object> getGenericData(Uri uri, String[] proj, int[] types)
throws NotFoundException {
return getGenericData(uri, proj, types, null);
}
private HashMap<String, Object> getGenericData(Uri uri, String[] proj, int[] types, String selection)
throws NotFoundException {
Cursor cursor = contentResolver.query(uri, proj, selection, null, null);
try {
HashMap<String, Object> result = new HashMap<>(proj.length);
if (cursor != null && cursor.moveToFirst()) {
int pos = 0;
for (String p : proj) {
switch (types[pos]) {
case FIELD_TYPE_NULL:
result.put(p, cursor.isNull(pos));
break;
case FIELD_TYPE_INTEGER:
result.put(p, cursor.getLong(pos));
break;
case FIELD_TYPE_FLOAT:
result.put(p, cursor.getFloat(pos));
break;
case FIELD_TYPE_STRING:
result.put(p, cursor.getString(pos));
break;
case FIELD_TYPE_BLOB:
result.put(p, cursor.getBlob(pos));
break;
}
pos += 1;
}
} else {
// If no data was found, throw an appropriate exception
throw new NotFoundException();
}
return result;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public HashMap<String, Object> getUnifiedData(long masterKeyId, String[] proj, int[] types)
throws NotFoundException {
return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types);
}
public long getMasterKeyId(long subKeyId) throws NotFoundException {
return (Long) getGenericData(KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId),
KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
}
public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException {
long masterKeyId = new CachedPublicKeyRing(this, queryUri).extractOrGetMasterKeyId();
return getCachedPublicKeyRing(masterKeyId);
}
public CachedPublicKeyRing getCachedPublicKeyRing(long id) {
return new CachedPublicKeyRing(this, KeyRings.buildUnifiedKeyRingUri(id));
}
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long id) throws NotFoundException {
return getCanonicalizedPublicKeyRing(KeyRings.buildUnifiedKeyRingUri(id));
}
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri queryUri) throws NotFoundException {
Cursor cursor = contentResolver.query(queryUri,
new String[] { KeyRings.MASTER_KEY_ID, KeyRings.VERIFIED }, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long masterKeyId = cursor.getLong(0);
int verified = cursor.getInt(1);
byte[] publicKeyData = loadPublicKeyRingData(masterKeyId);
return new CanonicalizedPublicKeyRing(publicKeyData, verified);
} else {
throw new NotFoundException("Key not found!");
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long id) throws NotFoundException {
return getCanonicalizedSecretKeyRing(KeyRings.buildUnifiedKeyRingUri(id));
}
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri queryUri) throws NotFoundException {
Cursor cursor = contentResolver.query(queryUri,
new String[] { KeyRings.MASTER_KEY_ID, KeyRings.VERIFIED, KeyRings.HAS_ANY_SECRET }, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long masterKeyId = cursor.getLong(0);
int verified = cursor.getInt(1);
int hasAnySecret = cursor.getInt(2);
if (hasAnySecret == 0) {
throw new NotFoundException("No secret key available or unknown public key!");
}
byte[] secretKeyData = loadSecretKeyRingData(masterKeyId);
return new CanonicalizedSecretKeyRing(secretKeyData, verified);
} else {
throw new NotFoundException("Key not found!");
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public ArrayList<String> getConfirmedUserIds(long masterKeyId) throws NotFoundException {
Cursor cursor = contentResolver.query(UserPackets.buildUserIdsUri(masterKeyId),
new String[]{UserPackets.USER_ID}, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null
);
if (cursor == null) {
throw new NotFoundException("Key id for requested user ids not found");
}
try {
ArrayList<String> userIds = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
String userId = cursor.getString(0);
userIds.add(userId);
}
return userIds;
} finally {
cursor.close();
}
}
private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException, PgpGeneralException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
aos.write(data);
aos.close();
return bos.toByteArray();
}
public String getPublicKeyRingAsArmoredString(long masterKeyId)
throws NotFoundException, IOException, PgpGeneralException {
byte[] data = loadPublicKeyRingData(masterKeyId);
byte[] armoredData = getKeyRingAsArmoredData(data);
return new String(armoredData);
}
public byte[] getSecretKeyRingAsArmoredData(long masterKeyId)
throws NotFoundException, IOException, PgpGeneralException {
byte[] data = loadSecretKeyRingData(masterKeyId);
return getKeyRingAsArmoredData(data);
}
public ContentResolver getContentResolver() {
return contentResolver;
}
@Nullable
Long getLastUpdateTime(long masterKeyId) {
Cursor lastUpdatedCursor = contentResolver.query(
UpdatedKeys.CONTENT_URI,
new String[] { UpdatedKeys.LAST_UPDATED },
UpdatedKeys.MASTER_KEY_ID + " = ?",
new String[] { "" + masterKeyId },
null
);
if (lastUpdatedCursor == null) {
return null;
}
Long lastUpdateTime;
try {
if (!lastUpdatedCursor.moveToNext()) {
return null;
}
lastUpdateTime = lastUpdatedCursor.getLong(0);
} finally {
lastUpdatedCursor.close();
}
return lastUpdateTime;
}
public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException {
byte[] data = (byte[]) getGenericDataOrNull(KeyRingData.buildPublicKeyRingUri(masterKeyId),
KeyRingData.KEY_RING_DATA, FIELD_TYPE_BLOB);
if (data == null) {
try {
data = mLocalPublicKeyStorage.readPublicKey(masterKeyId);
} catch (IOException e) {
Timber.e(e, "Error reading public key from storage!");
throw new NotFoundException();
}
}
if (data == null) {
throw new NotFoundException();
}
return data;
}
public final byte[] loadSecretKeyRingData(long masterKeyId) throws NotFoundException {
byte[] data = (byte[]) getGenericDataOrNull(KeychainContract.KeyRingData.buildSecretKeyRingUri(masterKeyId),
KeyRingData.KEY_RING_DATA, FIELD_TYPE_BLOB);
if (data == null) {
throw new NotFoundException();
}
return data;
}
public static class NotFoundException extends Exception {
public NotFoundException() {
}
public NotFoundException(String name) {
super(name);
}
}
}

View File

@@ -24,12 +24,12 @@ import org.sufficientlysecure.keychain.Constants;
public class KeychainContract {
interface KeyRingsColumns {
public interface KeyRingsColumns {
String MASTER_KEY_ID = "master_key_id"; // not a database id
String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
}
interface KeysColumns {
public interface KeysColumns {
String MASTER_KEY_ID = "master_key_id"; // not a database id
String RANK = "rank";
@@ -51,18 +51,12 @@ public class KeychainContract {
String EXPIRY = "expiry";
}
interface UpdatedKeysColumns {
String MASTER_KEY_ID = "master_key_id"; // not a database id
String LAST_UPDATED = "last_updated"; // time since epoch in seconds
String SEEN_ON_KEYSERVERS = "seen_on_keyservers";
}
interface KeySignaturesColumns {
public interface KeySignaturesColumns {
String MASTER_KEY_ID = "master_key_id"; // not a database id
String SIGNER_KEY_ID = "signer_key_id";
}
interface UserPacketsColumns {
public interface UserPacketsColumns {
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
String TYPE = "type"; // not a database id
String USER_ID = "user_id"; // not a database id
@@ -75,7 +69,7 @@ public class KeychainContract {
String IS_REVOKED = "is_revoked";
}
interface CertsColumns {
public interface CertsColumns {
String MASTER_KEY_ID = "master_key_id";
String RANK = "rank";
String KEY_ID_CERTIFIER = "key_id_certifier";
@@ -85,34 +79,15 @@ public class KeychainContract {
String DATA = "data";
}
interface ApiAppsColumns {
String PACKAGE_NAME = "package_name";
String PACKAGE_CERTIFICATE = "package_signature";
}
interface ApiAppsAllowedKeysColumns {
public interface ApiAppsAllowedKeysColumns {
String KEY_ID = "key_id"; // not a database id
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
}
interface OverriddenWarnings {
public interface OverriddenWarnings {
String IDENTIFIER = "identifier";
}
interface ApiAutocryptPeerColumns {
String PACKAGE_NAME = "package_name";
String IDENTIFIER = "identifier";
String LAST_SEEN = "last_seen";
String MASTER_KEY_ID = "master_key_id";
String LAST_SEEN_KEY = "last_seen_key";
String IS_MUTUAL = "is_mutual";
String GOSSIP_MASTER_KEY_ID = "gossip_master_key_id";
String GOSSIP_LAST_SEEN_KEY = "gossip_last_seen_key";
String GOSSIP_ORIGIN = "gossip_origin";
}
public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY;
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
@@ -120,181 +95,40 @@ public class KeychainContract {
public static final String BASE_KEY_RINGS = "key_rings";
public static final String BASE_UPDATED_KEYS = "updated_keys";
public static final String BASE_KEY_SIGNATURES = "key_signatures";
public static final String PATH_UNIFIED = "unified";
public static final String PATH_FIND = "find";
public static final String PATH_BY_EMAIL = "email";
public static final String PATH_BY_SUBKEY = "subkey";
public static final String PATH_BY_USER_ID = "user_id";
public static final String PATH_FILTER = "filter";
public static final String PATH_BY_SIGNER = "signer";
public static final String PATH_PUBLIC = "public";
public static final String PATH_SECRET = "secret";
public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_LINKED_IDS = "linked_ids";
public static final String PATH_KEYS = "keys";
public static final String PATH_CERTS = "certs";
public static final String BASE_API_APPS = "api_apps";
public static final String PATH_ALLOWED_KEYS = "allowed_keys";
public static final String PATH_BY_PACKAGE_NAME = "by_package_name";
public static final String PATH_BY_KEY_ID = "by_key_id";
public static final String BASE_AUTOCRYPT_PEERS = "autocrypt_peers";
public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns {
public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID;
public static final String IS_REVOKED = KeysColumns.IS_REVOKED;
public static final String IS_SECURE = KeysColumns.IS_SECURE;
public static final String VERIFIED = CertsColumns.VERIFIED;
public static final String IS_EXPIRED = "is_expired";
public static final String HAS_ANY_SECRET = "has_any_secret";
public static final String HAS_ENCRYPT = "has_encrypt";
public static final String HAS_SIGN_SECRET = "has_sign_secret";
public static final String HAS_CERTIFY_SECRET = "has_certify_secret";
public static final String HAS_AUTHENTICATE = "has_authenticate";
public static final String HAS_AUTHENTICATE_SECRET = "has_authenticate_secret";
public static final String HAS_DUPLICATE_USER_ID = "has_duplicate_user_id";
public static final String API_KNOWN_TO_PACKAGE_NAMES = "known_to_apps";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_rings";
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.key_rings";
public static Uri buildUnifiedKeyRingsUri() {
return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build();
}
public static Uri buildGenericKeyRingUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build();
}
public static Uri buildGenericKeyRingUri(String masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(masterKeyId).build();
}
public static Uri buildGenericKeyRingUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).build();
}
public static Uri buildUnifiedKeyRingUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId))
.appendPath(PATH_UNIFIED).build();
}
public static Uri buildUnifiedKeyRingUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1))
.appendPath(PATH_UNIFIED).build();
}
public static Uri buildUnifiedKeyRingsFindByEmailUri(String email) {
return CONTENT_URI.buildUpon().appendPath(PATH_FIND)
.appendPath(PATH_BY_EMAIL).appendPath(email).build();
}
public static Uri buildUnifiedKeyRingsFindByUserIdUri(String query) {
return CONTENT_URI.buildUpon().appendPath(PATH_FIND)
.appendPath(PATH_BY_USER_ID).appendPath(query).build();
}
public static Uri buildUnifiedKeyRingsFindBySubkeyUri(long subkey) {
return CONTENT_URI.buildUpon().appendPath(PATH_FIND)
.appendPath(PATH_BY_SUBKEY).appendPath(Long.toString(subkey)).build();
}
public static Uri buildUnifiedKeyRingsFilterBySigner() {
return CONTENT_URI.buildUpon().appendPath(PATH_FILTER).appendPath(PATH_BY_SIGNER).build();
}
}
public static class KeyRingData implements KeyRingsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_ring_data";
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.key_ring_data";
public static Uri buildPublicKeyRingUri() {
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
}
public static Uri buildPublicKeyRingUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_PUBLIC).build();
}
public static Uri buildPublicKeyRingUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_PUBLIC).build();
}
public static Uri buildSecretKeyRingUri() {
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
}
public static Uri buildSecretKeyRingUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_SECRET).build();
}
public static Uri buildSecretKeyRingUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_SECRET).build();
}
}
public static class Keys implements KeysColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.keychain.keys";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.keychain.keys";
public static Uri buildKeysUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build();
}
public static Uri buildKeysUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_KEYS).build();
}
}
public static class UpdatedKeys implements UpdatedKeysColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_UPDATED_KEYS).build();
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
}
public static class KeySignatures implements KeySignaturesColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_SIGNATURES).build();
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_signatures";
}
public static class UserPackets implements UserPacketsColumns, BaseColumns {
@@ -302,116 +136,12 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.user_ids";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.user_ids";
public static Uri buildUserIdsUri() {
return CONTENT_URI.buildUpon().appendPath(PATH_USER_IDS).build();
}
public static Uri buildUserIdsUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build();
}
public static Uri buildUserIdsUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();
}
public static Uri buildLinkedIdsUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_LINKED_IDS).build();
}
public static Uri buildLinkedIdsUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_LINKED_IDS).build();
}
}
public static class ApiApps implements ApiAppsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.api_apps";
public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build();
}
}
public static class ApiAllowedKeys implements ApiAppsAllowedKeysColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps.allowed_keys";
public static Uri buildBaseUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ALLOWED_KEYS)
.build();
}
}
public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_AUTOCRYPT_PEERS).build();
public static final String KEY_IS_REVOKED = "key_is_revoked";
public static final String KEY_IS_EXPIRED = "key_is_expired";
public static final String KEY_IS_VERIFIED = "key_is_verified";
public static final String GOSSIP_KEY_IS_REVOKED = "gossip_key_is_revoked";
public static final String GOSSIP_KEY_IS_EXPIRED = "gossip_key_is_expired";
public static final String GOSSIP_KEY_IS_VERIFIED = "gossip_key_is_verified";
public static final int GOSSIP_ORIGIN_AUTOCRYPT = 0;
public static final int GOSSIP_ORIGIN_SIGNATURE = 10;
public static final int GOSSIP_ORIGIN_DEDUP = 20;
public static Uri buildByKeyUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).build();
}
public static Uri buildByPackageName(String packageName) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).build();
}
public static Uri buildByPackageNameAndAutocryptId(String packageName, String autocryptPeer) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).appendPath(autocryptPeer).build();
}
public static Uri buildByMasterKeyId(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(Long.toString(masterKeyId)).build();
}
}
public static class Certs implements CertsColumns, BaseColumns {
public static final String USER_ID = UserPacketsColumns.USER_ID;
public static final String NAME = UserPacketsColumns.NAME;
public static final String EMAIL = UserPacketsColumns.EMAIL;
public static final String COMMENT = UserPacketsColumns.COMMENT;
public static final String SIGNER_UID = "signer_user_id";
public static final int UNVERIFIED = 0;
public static final int VERIFIED_SECRET = 1;
public static final int VERIFIED_SELF = 2;
@@ -421,24 +151,6 @@ public class KeychainContract {
public static Uri buildCertsUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_CERTS).build();
}
public static Uri buildCertsSpecificUri(long masterKeyId, long rank, long certifier) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId))
.appendPath(PATH_CERTS).appendPath(Long.toString(rank))
.appendPath(Long.toString(certifier)).build();
}
public static Uri buildCertsUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1))
.appendPath(PATH_CERTS).build();
}
public static Uri buildLinkedIdCertsUri(Uri uri, int rank) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1))
.appendPath(PATH_LINKED_IDS).appendPath(Integer.toString(rank))
.appendPath(PATH_CERTS).build();
}
}
private KeychainContract() {

View File

@@ -22,7 +22,6 @@ import android.net.Uri;
import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
public class KeychainExternalContract {

View File

@@ -1,78 +0,0 @@
package org.sufficientlysecure.keychain.provider;
import java.util.GregorianCalendar;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
public class LastUpdateInteractor {
private final ContentResolver contentResolver;
private final DatabaseNotifyManager databaseNotifyManager;
public static LastUpdateInteractor create(Context context) {
return new LastUpdateInteractor(context.getContentResolver(), DatabaseNotifyManager.create(context));
}
private LastUpdateInteractor(ContentResolver contentResolver, DatabaseNotifyManager databaseNotifyManager) {
this.contentResolver = contentResolver;
this.databaseNotifyManager = databaseNotifyManager;
}
@Nullable
public Boolean getSeenOnKeyservers(long masterKeyId) {
Cursor cursor = contentResolver.query(
UpdatedKeys.CONTENT_URI,
new String[] { UpdatedKeys.SEEN_ON_KEYSERVERS },
UpdatedKeys.MASTER_KEY_ID + " = ?",
new String[] { "" + masterKeyId },
null
);
if (cursor == null) {
return null;
}
Boolean seenOnKeyservers;
try {
if (!cursor.moveToNext()) {
return null;
}
seenOnKeyservers = cursor.isNull(0) ? null : cursor.getInt(0) != 0;
} finally {
cursor.close();
}
return seenOnKeyservers;
}
public void resetAllLastUpdatedTimes() {
ContentValues values = new ContentValues();
values.putNull(UpdatedKeys.LAST_UPDATED);
values.putNull(UpdatedKeys.SEEN_ON_KEYSERVERS);
contentResolver.update(UpdatedKeys.CONTENT_URI, values, null, null);
}
public Uri renewKeyLastUpdatedTime(long masterKeyId, boolean seenOnKeyservers) {
boolean isFirstKeyserverStatusCheck = getSeenOnKeyservers(masterKeyId) == null;
ContentValues values = new ContentValues();
values.put(UpdatedKeys.MASTER_KEY_ID, masterKeyId);
values.put(UpdatedKeys.LAST_UPDATED, GregorianCalendar.getInstance().getTimeInMillis() / 1000);
if (seenOnKeyservers || isFirstKeyserverStatusCheck) {
values.put(UpdatedKeys.SEEN_ON_KEYSERVERS, seenOnKeyservers);
}
// this will actually update/replace, doing the right thing™ for seenOnKeyservers value
// see `KeychainProvider.insert()`
Uri insert = contentResolver.insert(UpdatedKeys.CONTENT_URI, values);
databaseNotifyManager.notifyKeyserverStatusChange(masterKeyId);
return insert;
}
}

View File

@@ -1,80 +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.provider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
public class OverriddenWarningsRepository {
private final Context context;
private KeychainDatabase keychainDatabase;
public static OverriddenWarningsRepository createOverriddenWarningsRepository(Context context) {
return new OverriddenWarningsRepository(context);
}
private OverriddenWarningsRepository(Context context) {
this.context = context;
}
private KeychainDatabase getDb() {
if (keychainDatabase == null) {
keychainDatabase = new KeychainDatabase(context);
}
return keychainDatabase;
}
public boolean isWarningOverridden(String identifier) {
SQLiteDatabase db = getDb().getReadableDatabase();
Cursor cursor = db.query(
Tables.OVERRIDDEN_WARNINGS,
new String[] { "COUNT(*)" },
OverriddenWarnings.IDENTIFIER + " = ?",
new String[] { identifier },
null, null, null);
try {
cursor.moveToFirst();
return cursor.getInt(0) > 0;
} finally {
cursor.close();
db.close();
}
}
public void putOverride(String identifier) {
SQLiteDatabase db = getDb().getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(OverriddenWarnings.IDENTIFIER, identifier);
db.replace(Tables.OVERRIDDEN_WARNINGS, null, cv);
db.close();
}
public void deleteOverride(String identifier) {
SQLiteDatabase db = getDb().getWritableDatabase();
db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier });
db.close();
}
}

View File

@@ -1,39 +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.provider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
/** This interface contains the principal methods for database access
* from {#android.content.ContentResolver}. It is used to allow substitution
* of a ContentResolver in DAOs.
*
* @see ApiDataAccessObject
*/
public interface SimpleContentResolverInterface {
Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
Uri insert(Uri contentUri, ContentValues values);
int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs);
int delete(Uri contentUri, String where, String[] selectionArgs);
}

View File

@@ -18,8 +18,17 @@
package org.sufficientlysecure.keychain.provider;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -29,18 +38,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.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.DatabaseUtil;
import timber.log.Timber;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* TemporaryStorageProvider stores decrypted files inside the app's cache directory previously to
* sharing them with other applications.
@@ -81,16 +87,20 @@ public class TemporaryFileProvider extends ContentProvider {
private static Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9-]+");
public static Uri createFile(Context context, String targetName, String mimeType) {
ContentResolver contentResolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(TemporaryFileColumns.COLUMN_NAME, targetName);
contentValues.put(TemporaryFileColumns.COLUMN_TYPE, mimeType);
return context.getContentResolver().insert(CONTENT_URI, contentValues);
contentValues.put(TemporaryFileColumns.COLUMN_TIME, System.currentTimeMillis());
Uri resultUri = contentResolver.insert(CONTENT_URI, contentValues);
scheduleCleanupAfterTtl();
return resultUri;
}
public static Uri createFile(Context context, String targetName) {
ContentValues contentValues = new ContentValues();
contentValues.put(TemporaryFileColumns.COLUMN_NAME, targetName);
return context.getContentResolver().insert(CONTENT_URI, contentValues);
return createFile(context, targetName, null);
}
public static Uri createFile(Context context) {
@@ -110,15 +120,6 @@ public class TemporaryFileProvider extends ContentProvider {
return context.getContentResolver().update(uri, values, null, null);
}
public static int cleanUp(Context context) {
Timber.d("Cleaning up temporary files…");
return context.getContentResolver().delete(
CONTENT_URI,
TemporaryFileColumns.COLUMN_TIME + "< ?",
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}
);
}
private class TemporaryStorageDatabase extends SQLiteOpenHelper {
public TemporaryStorageDatabase(Context context) {
@@ -247,9 +248,6 @@ public class TemporaryFileProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
if (!values.containsKey(TemporaryFileColumns.COLUMN_TIME)) {
values.put(TemporaryFileColumns.COLUMN_TIME, System.currentTimeMillis());
}
String uuid = UUID.randomUUID().toString();
values.put(TemporaryFileColumns.COLUMN_UUID, uuid);
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
@@ -310,4 +308,34 @@ public class TemporaryFileProvider extends ContentProvider {
return openFileHelper(uri, mode);
}
public static void scheduleCleanupAfterTtl() {
OneTimeWorkRequest cleanupWork = new OneTimeWorkRequest.Builder(CleanupWorker.class)
.setInitialDelay(Constants.TEMPFILE_TTL, TimeUnit.MILLISECONDS).build();
WorkManager.getInstance().enqueue(cleanupWork);
}
public static void scheduleCleanupImmediately() {
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);
}
}
public static class CleanupWorker extends Worker {
@NonNull
@Override
public WorkerResult doWork() {
Timber.d("Cleaning up temporary files…");
ContentResolver contentResolver = getApplicationContext().getContentResolver();
contentResolver.delete(
CONTENT_URI,
TemporaryFileColumns.COLUMN_TIME + " <= ?",
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}
);
return WorkerResult.SUCCESS;
}
}
}

View File

@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.remote;
import java.util.ArrayList;
import android.app.PendingIntent;
@@ -25,7 +26,6 @@ import android.content.Intent;
import android.os.Build;
import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.remote.ui.RemoteBackupActivity;
import org.sufficientlysecure.keychain.remote.ui.RemoteDisplayTransferCodeActivity;
import org.sufficientlysecure.keychain.remote.ui.RemoteErrorActivity;
@@ -92,8 +92,9 @@ public class ApiPendingIntentFactory {
return createInternal(data, intent);
}
PendingIntent createSelectPublicKeyPendingIntent(Intent data, long[] keyIdsArray, ArrayList<String> missingEmails,
ArrayList<String> duplicateEmails, boolean noUserIdsCheck) {
public PendingIntent createSelectPublicKeyPendingIntent(Intent data, long[] keyIdsArray,
ArrayList<String> missingEmails,
ArrayList<String> duplicateEmails, boolean noUserIdsCheck) {
Intent intent = new Intent(mContext, RemoteSelectPubKeyActivity.class);
intent.putExtra(RemoteSelectPubKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
intent.putExtra(RemoteSelectPubKeyActivity.EXTRA_NO_USER_IDS_CHECK, noUserIdsCheck);
@@ -103,7 +104,7 @@ public class ApiPendingIntentFactory {
return createInternal(data, intent);
}
PendingIntent createDeduplicatePendingIntent(String packageName, Intent data, ArrayList<String> duplicateEmails) {
public PendingIntent createDeduplicatePendingIntent(String packageName, Intent data, ArrayList<String> duplicateEmails) {
Intent intent = new Intent(mContext, RemoteDeduplicateActivity.class);
intent.putExtra(RemoteDeduplicateActivity.EXTRA_PACKAGE_NAME, packageName);
@@ -121,7 +122,7 @@ public class ApiPendingIntentFactory {
return createInternal(data, intent);
}
PendingIntent createRequestKeyPermissionPendingIntent(Intent data, String packageName, long... masterKeyIds) {
public PendingIntent createRequestKeyPermissionPendingIntent(Intent data, String packageName, long... masterKeyIds) {
Intent intent = new Intent(mContext, RequestKeyPermissionActivity.class);
intent.putExtra(RequestKeyPermissionActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RequestKeyPermissionActivity.EXTRA_REQUESTED_KEY_IDS, masterKeyIds);
@@ -130,24 +131,23 @@ public class ApiPendingIntentFactory {
}
PendingIntent createShowKeyPendingIntent(Intent data, long masterKeyId) {
Intent intent = new Intent(mContext, ViewKeyActivity.class);
intent.setData(KeychainContract.KeyRings.buildGenericKeyRingUri(masterKeyId));
Intent intent = ViewKeyActivity.getViewKeyActivityIntent(mContext, masterKeyId);
return createInternal(data, intent);
}
PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) {
public PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName,
String preferredUserId) {
Intent intent = new Intent(mContext, SelectSignKeyIdActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
intent.putExtra(SelectSignKeyIdActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
return createInternal(data, intent);
}
PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName,
public PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName,
byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) {
Intent intent = new Intent(mContext, RemoteSelectIdKeyActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_USER_ID, preferredUserId);
@@ -156,9 +156,8 @@ public class ApiPendingIntentFactory {
return createInternal(data, intent);
}
PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) {
public PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) {
Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName);
return createInternal(data, intent);
@@ -220,7 +219,7 @@ public class ApiPendingIntentFactory {
}
}
PendingIntent createRegisterPendingIntent(Intent data, String packageName, byte[] packageCertificate) {
public PendingIntent createRegisterPendingIntent(Intent data, String packageName, byte[] packageCertificate) {
Intent intent = new Intent(mContext, RemoteRegisterActivity.class);
intent.putExtra(RemoteRegisterActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RemoteRegisterActivity.EXTRA_PACKAGE_SIGNATURE, packageCertificate);

View File

@@ -35,7 +35,7 @@ import android.os.Binder;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import timber.log.Timber;
@@ -45,13 +45,13 @@ import timber.log.Timber;
public class ApiPermissionHelper {
private final Context mContext;
private final ApiDataAccessObject mApiDao;
private final ApiAppDao mApiAppDao;
private PackageManager mPackageManager;
public ApiPermissionHelper(Context context, ApiDataAccessObject apiDao) {
public ApiPermissionHelper(Context context, ApiAppDao apiAppDao) {
mContext = context;
mPackageManager = context.getPackageManager();
mApiDao = apiDao;
mApiAppDao = apiAppDao;
}
public static class WrongPackageCertificateException extends Exception {
@@ -206,7 +206,7 @@ public class ApiPermissionHelper {
public boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {
Timber.d("isPackageAllowed packageName: " + packageName);
byte[] storedPackageCert = mApiDao.getApiAppCertificate(packageName);
byte[] storedPackageCert = mApiAppDao.getApiAppCertificate(packageName);
boolean isKnownPackage = storedPackageCert != null;
if (!isKnownPackage) {

View File

@@ -1,50 +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.remote;
public class AppSettings {
private String mPackageName;
private byte[] mPackageCertificate;
public AppSettings() {
}
public AppSettings(String packageName, byte[] packageSignature) {
super();
this.mPackageName = packageName;
this.mPackageCertificate = packageSignature;
}
public String getPackageName() {
return mPackageName;
}
public void setPackageName(String packageName) {
this.mPackageName = packageName;
}
public byte[] getPackageCertificate() {
return mPackageCertificate;
}
public void setPackageCertificate(byte[] packageCertificate) {
this.mPackageCertificate = packageCertificate;
}
}

View File

@@ -2,52 +2,64 @@ package org.sufficientlysecure.keychain.remote;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.support.annotation.Nullable;
import android.text.format.DateUtils;
import org.openintents.openpgp.AutocryptPeerUpdate;
import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.model.AutocryptPeer;
import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus;
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
import timber.log.Timber;
class AutocryptInteractor {
public class AutocryptInteractor {
private static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS;
private AutocryptPeerDataAccessObject autocryptPeerDao;
private AutocryptPeerDao autocryptPeerDao;
private KeyWritableRepository keyWritableRepository;
public static AutocryptInteractor getInstance(Context context, AutocryptPeerDataAccessObject autocryptPeerentityDao) {
private final String packageName;
public static AutocryptInteractor getInstance(Context context, String packageName) {
AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context);
KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(context);
return new AutocryptInteractor(autocryptPeerentityDao, keyWritableRepository);
return new AutocryptInteractor(autocryptPeerDao, keyWritableRepository, packageName);
}
private AutocryptInteractor(AutocryptPeerDataAccessObject autocryptPeerDao,
KeyWritableRepository keyWritableRepository) {
private AutocryptInteractor(AutocryptPeerDao autocryptPeerDao,
KeyWritableRepository keyWritableRepository, String packageName) {
this.autocryptPeerDao = autocryptPeerDao;
this.keyWritableRepository = keyWritableRepository;
this.packageName = packageName;
}
void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) {
AutocryptPeer currentAutocryptPeer = autocryptPeerDao.getAutocryptPeer(packageName, autocryptPeerId);
Date effectiveDate = autocryptPeerUpdate.getEffectiveDate();
// 1. If the messages effective date is older than the peers[from-addr].autocrypt_timestamp value, then no changes are required, and the update process terminates.
Date lastSeenAutocrypt = autocryptPeerDao.getLastSeenKey(autocryptPeerId);
if (lastSeenAutocrypt != null && effectiveDate.compareTo(lastSeenAutocrypt) <= 0) {
Date lastSeenKey = currentAutocryptPeer != null ? currentAutocryptPeer.last_seen_key() : null;
if (lastSeenKey != null && effectiveDate.compareTo(lastSeenKey) <= 0) {
return;
}
// 2. If the messages effective date is more recent than peers[from-addr].last_seen then set peers[from-addr].last_seen to the messages effective date.
Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId);
Date lastSeen = currentAutocryptPeer != null ? currentAutocryptPeer.last_seen() : null;
if (lastSeen == null || effectiveDate.after(lastSeen)) {
autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate);
autocryptPeerDao.insertOrUpdateLastSeen(packageName, autocryptPeerId, effectiveDate);
}
// 3. If the Autocrypt header is unavailable, no further changes are required and the update process terminates.
@@ -66,17 +78,18 @@ class AutocryptInteractor {
// 6. Set peers[from-addr].prefer_encrypt to the corresponding prefer-encrypt value of the Autocrypt header.
boolean isMutual = autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL;
autocryptPeerDao.updateKey(autocryptPeerId, effectiveDate, newMasterKeyId, isMutual);
autocryptPeerDao.updateKey(packageName, autocryptPeerId, effectiveDate, newMasterKeyId, isMutual);
}
void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) {
AutocryptPeer currentAutocryptPeer = autocryptPeerDao.getAutocryptPeer(packageName, autocryptPeerId);
Date effectiveDate = autocryptPeerUpdate.getEffectiveDate();
// 1. If gossip-addr does not match any recipient in the mails To or Cc header, the update process terminates (i.e., header is ignored).
// -> This should be taken care of in the mail client that sends us this data!
// 2. If peers[gossip-addr].gossip_timestamp is more recent than the messages effective date, then the update process terminates.
Date lastSeenGossip = autocryptPeerDao.getLastSeenGossip(autocryptPeerId);
Date lastSeenGossip = currentAutocryptPeer != null ? currentAutocryptPeer.gossip_last_seen_key() : null;
if (lastSeenGossip != null && lastSeenGossip.after(effectiveDate)) {
return;
}
@@ -94,7 +107,8 @@ class AutocryptInteractor {
// 4. Set peers[gossip-addr].gossip_key to the value of the keydata attribute.
Long newMasterKeyId = saveKeyringResult.savedMasterKeyId;
autocryptPeerDao.updateKeyGossipFromAutocrypt(autocryptPeerId, effectiveDate, newMasterKeyId);
autocryptPeerDao.updateKeyGossip(packageName, autocryptPeerId, effectiveDate, newMasterKeyId,
GossipOrigin.GOSSIP_HEADER);
}
@Nullable
@@ -131,4 +145,100 @@ class AutocryptInteractor {
}
return uncachedKeyRing;
}
public List<AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) {
List<AutocryptRecommendationResult> result = new ArrayList<>(autocryptIds.length);
for (AutocryptKeyStatus autocryptKeyStatus : autocryptPeerDao.getAutocryptKeyStatus(packageName, autocryptIds)) {
AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(autocryptKeyStatus);
result.add(peerResult);
}
return result;
}
/** Determines Autocrypt "ui-recommendation", according to spec.
* See https://autocrypt.org/level1.html#recommendations-for-single-recipient-messages
*/
private AutocryptRecommendationResult determineAutocryptRecommendation(AutocryptKeyStatus autocryptKeyStatus) {
AutocryptRecommendationResult keyRecommendation = determineAutocryptKeyRecommendation(autocryptKeyStatus);
if (keyRecommendation != null) return keyRecommendation;
AutocryptRecommendationResult gossipRecommendation = determineAutocryptGossipRecommendation(autocryptKeyStatus);
if (gossipRecommendation != null) return gossipRecommendation;
return new AutocryptRecommendationResult(autocryptKeyStatus.autocryptPeer().identifier(), AutocryptState.DISABLE, null, false);
}
@Nullable
private AutocryptRecommendationResult determineAutocryptKeyRecommendation(AutocryptKeyStatus autocryptKeyStatus) {
AutocryptPeer autocryptPeer = autocryptKeyStatus.autocryptPeer();
Long masterKeyId = autocryptPeer.master_key_id();
boolean hasKey = masterKeyId != null;
boolean isRevoked = autocryptKeyStatus.isKeyRevoked();
boolean isExpired = autocryptKeyStatus.isKeyExpired();
if (!hasKey || isRevoked || isExpired) {
return null;
}
Date lastSeen = autocryptPeer.last_seen();
Date lastSeenKey = autocryptPeer.last_seen_key();
boolean isVerified = autocryptKeyStatus.isKeyVerified();
boolean isLastSeenOlderThanDiscourageTimespan = lastSeen != null && lastSeenKey != null &&
lastSeenKey.getTime() < (lastSeen.getTime() - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS);
if (isLastSeenOlderThanDiscourageTimespan) {
return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified);
}
boolean isMutual = autocryptPeer.is_mutual();
if (isMutual) {
return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.MUTUAL, masterKeyId, isVerified);
} else {
return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.AVAILABLE, masterKeyId, isVerified);
}
}
@Nullable
private AutocryptRecommendationResult determineAutocryptGossipRecommendation(AutocryptKeyStatus autocryptKeyStatus) {
boolean gossipHasKey = autocryptKeyStatus.hasGossipKey();
boolean gossipIsRevoked = autocryptKeyStatus.isGossipKeyRevoked();
boolean gossipIsExpired = autocryptKeyStatus.isGossipKeyExpired();
boolean isVerified = autocryptKeyStatus.isGossipKeyVerified();
if (!gossipHasKey || gossipIsRevoked || gossipIsExpired) {
return null;
}
Long masterKeyId = autocryptKeyStatus.autocryptPeer().gossip_master_key_id();
return new AutocryptRecommendationResult(autocryptKeyStatus.autocryptPeer().identifier(), AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified);
}
public void updateKeyGossipFromSignature(String autocryptId, Date effectiveDate, long masterKeyId) {
autocryptPeerDao.updateKeyGossip(packageName, autocryptId, effectiveDate, masterKeyId, GossipOrigin.SIGNATURE);
}
public void updateKeyGossipFromDedup(String autocryptId, long masterKeyId) {
autocryptPeerDao.updateKeyGossip(packageName, autocryptId, new Date(), masterKeyId, GossipOrigin.DEDUP);
}
public static class AutocryptRecommendationResult {
public final String peerId;
public final Long masterKeyId;
public final AutocryptState autocryptState;
public final boolean isVerified;
AutocryptRecommendationResult(String peerId, AutocryptState autocryptState, Long masterKeyId,
boolean isVerified) {
this.peerId = peerId;
this.autocryptState = autocryptState;
this.masterKeyId = masterKeyId;
this.isVerified = isVerified;
}
}
public enum AutocryptState {
DISABLE, DISCOURAGED_OLD, DISCOURAGED_GOSSIP, AVAILABLE, MUTUAL
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -37,44 +38,33 @@ import android.text.TextUtils;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptRecommendationResult;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState;
import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.KeychainDatabase;
import org.sufficientlysecure.keychain.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.provider.KeychainProvider;
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
import org.sufficientlysecure.keychain.util.CloseDatabaseCursorFactory;
import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptRecommendationResult;
import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState;
import timber.log.Timber;
public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface {
public class KeychainExternalProvider extends ContentProvider {
private static final int EMAIL_STATUS = 101;
private static final int AUTOCRYPT_STATUS = 201;
private static final int AUTOCRYPT_STATUS_INTERNAL = 202;
private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 302;
public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses";
public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
private UriMatcher uriMatcher;
private ApiPermissionHelper apiPermissionHelper;
private KeychainProvider internalKeychainProvider;
private DatabaseNotifyManager databaseNotifyManager;
/**
@@ -111,10 +101,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
throw new NullPointerException("Context can't be null during onCreate!");
}
internalKeychainProvider = new KeychainProvider();
internalKeychainProvider.attachInfo(context, null);
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(internalKeychainProvider));
databaseNotifyManager = DatabaseNotifyManager.create(context);
apiPermissionHelper = new ApiPermissionHelper(context, ApiAppDao.getInstance(getContext()));
return true;
}
@@ -127,13 +114,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
switch (match) {
case EMAIL_STATUS:
return EmailStatus.CONTENT_TYPE;
case API_APPS:
return ApiApps.CONTENT_TYPE;
case API_APPS_BY_PACKAGE_NAME:
return ApiApps.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@@ -154,7 +134,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
String groupBy = null;
SQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase();
SupportSQLiteDatabase db = KeychainDatabase.getInstance(getContext()).getReadableDatabase();
String callingPackageName = apiPermissionHelper.getCurrentCallingPackage();
@@ -169,7 +149,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
ContentValues cv = new ContentValues();
for (String address : selectionArgs) {
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv);
}
HashMap<String, String> projectionMap = new HashMap<>();
@@ -256,7 +236,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
ContentValues cv = new ContentValues();
for (String address : selectionArgs) {
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv);
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv);
}
boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%");
@@ -278,10 +258,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!");
}
if (!isWildcardSelector && queriesAutocryptResult) {
AutocryptPeerDataAccessObject autocryptPeerDao =
new AutocryptPeerDataAccessObject(internalKeychainProvider, callingPackageName,
databaseNotifyManager);
fillTempTableWithAutocryptRecommendations(db, autocryptPeerDao, selectionArgs);
AutocryptInteractor autocryptInteractor =
AutocryptInteractor.getInstance(getContext(), callingPackageName);
fillTempTableWithAutocryptRecommendations(db, autocryptInteractor, selectionArgs);
}
HashMap<String, String> projectionMap = new HashMap<>();
@@ -321,8 +300,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
}
qb.setStrict(true);
qb.setCursorFactory(new CloseDatabaseCursorFactory());
Cursor cursor = qb.query(db, projection, null, null, groupBy, null, orderBy);
String query = qb.buildQuery(projection, null, groupBy, null, orderBy, null);
Cursor cursor = db.query(query);
if (cursor != null) {
// Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
@@ -337,15 +316,15 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
return cursor;
}
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) {
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
AutocryptInteractor autocryptInteractor, String[] peerIds) {
List<AutocryptRecommendationResult> autocryptStates =
autocryptPeerDao.determineAutocryptRecommendations(peerIds);
autocryptInteractor.determineAutocryptRecommendations(peerIds);
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
}
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
List<AutocryptRecommendationResult> autocryptRecommendations) {
ContentValues cv = new ContentValues();
for (AutocryptRecommendationResult peerResult : autocryptRecommendations) {
@@ -359,12 +338,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
KeychainExternalContract.KEY_STATUS_UNVERIFIED);
}
db.update(TEMP_TABLE_QUERIED_ADDRESSES, cv,TEMP_TABLE_COLUMN_ADDRES + "=?",
db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?",
new String[] { peerResult.peerId });
}
}
private void fillTempTableWithUidResult(SQLiteDatabase db, boolean isWildcardSelector) {
private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) {
String cmpOperator = isWildcardSelector ? " LIKE " : " = ";
long unixSeconds = System.currentTimeMillis() / 1000;
db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES +

View File

@@ -52,6 +52,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpSignatureResult.AutocryptPeerResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.BackupOperation;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
@@ -66,15 +67,12 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SecurityProblem;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus;
import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository;
import org.sufficientlysecure.keychain.daos.OverriddenWarningsDao;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus;
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
@@ -98,7 +96,7 @@ public class OpenPgpService extends Service {
private ApiPermissionHelper mApiPermissionHelper;
private KeyRepository mKeyRepository;
private ApiDataAccessObject mApiDao;
private ApiAppDao mApiAppDao;
private OpenPgpServiceKeyIdExtractor mKeyIdExtractor;
private ApiPendingIntentFactory mApiPendingIntentFactory;
@@ -106,8 +104,8 @@ public class OpenPgpService extends Service {
public void onCreate() {
super.onCreate();
mKeyRepository = KeyRepository.create(this);
mApiDao = new ApiDataAccessObject(this);
mApiPermissionHelper = new ApiPermissionHelper(this, mApiDao);
mApiAppDao = ApiAppDao.getInstance(this);
mApiPermissionHelper = new ApiPermissionHelper(this, mApiAppDao);
mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext());
mKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(getContentResolver(), mApiPendingIntentFactory);
}
@@ -140,9 +138,9 @@ public class OpenPgpService extends Service {
// get first usable subkey capable of signing
try {
long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(signKeyId).getSecretSignId();
long signSubKeyId = mKeyRepository.getSecretSignId(signKeyId);
pgpData.setSignatureSubKeyId(signSubKeyId);
} catch (PgpKeyNotFoundException e) {
} catch (NotFoundException e) {
throw new Exception("signing subkey not found!", e);
}
}
@@ -231,7 +229,7 @@ public class OpenPgpService extends Service {
if (signKeyId == Constants.key.none) {
throw new Exception("No signing key given");
}
long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(signKeyId).getSecretSignId();
long signSubKeyId = mKeyRepository.getSecretSignId(signKeyId);
pgpData.setSignatureMasterKeyId(signKeyId)
.setSignatureSubKeyId(signSubKeyId)
@@ -472,7 +470,7 @@ public class OpenPgpService extends Service {
SecurityProblem prioritySecurityProblem = securityProblem.getPrioritySecurityProblem();
if (prioritySecurityProblem.isIdentifiable()) {
String identifier = prioritySecurityProblem.getIdentifier();
boolean isOverridden = OverriddenWarningsRepository.createOverriddenWarningsRepository(this)
boolean isOverridden = OverriddenWarningsDao.create(this)
.isWarningOverridden(identifier);
result.putExtra(OpenPgpApi.RESULT_OVERRIDE_CRYPTO_WARNING, isOverridden);
}
@@ -585,8 +583,8 @@ public class OpenPgpService extends Service {
return signatureResult;
}
AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(),
mApiPermissionHelper.getCurrentCallingPackage());
AutocryptPeerDao autocryptPeerentityDao =
AutocryptPeerDao.getInstance(getBaseContext());
Long autocryptPeerMasterKeyId = autocryptPeerentityDao.getMasterKeyIdForAutocryptPeer(autocryptPeerId);
long masterKeyId = signatureResult.getKeyId();
@@ -596,7 +594,9 @@ public class OpenPgpService extends Service {
if (effectiveTime.after(now)) {
effectiveTime = now;
}
autocryptPeerentityDao.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId);
AutocryptInteractor autocryptInteractor =
AutocryptInteractor.getInstance(this, mApiPermissionHelper.getCurrentCallingPackage());
autocryptInteractor.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId);
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.NEW);
} else if (masterKeyId == autocryptPeerMasterKeyId) {
return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.OK);
@@ -626,10 +626,8 @@ public class OpenPgpService extends Service {
}
try {
// try to find key, throws NotFoundException if not in db!
CanonicalizedPublicKeyRing keyRing =
mKeyRepository.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(masterKeyId));
mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
@@ -744,17 +742,16 @@ public class OpenPgpService extends Service {
result.putExtra(OpenPgpApi.RESULT_SIGN_KEY_ID, signKeyId);
if (signKeyId != Constants.key.none) {
try {
CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(signKeyId);
String userId = cachedPublicKeyRing.getPrimaryUserId();
long creationTime = cachedPublicKeyRing.getCreationTime() * 1000;
result.putExtra(OpenPgpApi.RESULT_PRIMARY_USER_ID, userId);
result.putExtra(OpenPgpApi.RESULT_KEY_CREATION_TIME, creationTime);
} catch (PgpKeyNotFoundException e) {
Timber.e(e, "Error loading key info");
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage());
UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(signKeyId);
if (unifiedKeyInfo == null) {
Timber.e("Error loading key info");
return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, "Signing key not found!");
}
String userId = unifiedKeyInfo.user_id();
long creationTime = unifiedKeyInfo.creation() * 1000;
result.putExtra(OpenPgpApi.RESULT_PRIMARY_USER_ID, userId);
result.putExtra(OpenPgpApi.RESULT_KEY_CREATION_TIME, creationTime);
}
return result;
@@ -860,9 +857,8 @@ public class OpenPgpService extends Service {
private Intent updateAutocryptPeerImpl(Intent data) {
try {
AutocryptPeerDataAccessObject autocryptPeerDao = new AutocryptPeerDataAccessObject(getBaseContext(),
mApiPermissionHelper.getCurrentCallingPackage());
AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(getBaseContext(), autocryptPeerDao);
AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(
getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage());
if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) &&
data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) {
@@ -915,8 +911,7 @@ public class OpenPgpService extends Service {
private HashSet<Long> getAllowedKeyIds() {
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
return mApiDao.getAllowedKeyIdsForApp(
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
return mApiAppDao.getAllowedKeyIdsForApp(currentPkg);
}
/**

View File

@@ -17,12 +17,13 @@
package org.sufficientlysecure.keychain.remote;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
public class PackageUninstallReceiver extends BroadcastReceiver {
@@ -34,8 +35,9 @@ public class PackageUninstallReceiver extends BroadcastReceiver {
return;
}
String packageName = uri.getEncodedSchemeSpecificPart();
Uri appUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName);
context.getContentResolver().delete(appUri, null, null);
ApiAppDao apiAppDao = ApiAppDao.getInstance(context);
apiAppDao.deleteApiApp(packageName);
}
}
}

View File

@@ -17,11 +17,19 @@
package org.sufficientlysecure.keychain.remote;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.openintents.ssh.authentication.ISshAuthenticationService;
@@ -32,15 +40,15 @@ import org.openintents.ssh.authentication.response.PublicKeyResponse;
import org.openintents.ssh.authentication.response.SigningResponse;
import org.openintents.ssh.authentication.response.SshPublicKeyResponse;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.SshPublicKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ssh.AuthenticationData;
@@ -50,20 +58,11 @@ import org.sufficientlysecure.keychain.ssh.AuthenticationResult;
import org.sufficientlysecure.keychain.ssh.signature.SshSignatureConverter;
import timber.log.Timber;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
public class SshAuthenticationService extends Service {
private static final String TAG = "SshAuthService";
private ApiPermissionHelper mApiPermissionHelper;
private KeyRepository mKeyRepository;
private ApiDataAccessObject mApiDao;
private ApiAppDao mApiAppDao;
private ApiPendingIntentFactory mApiPendingIntentFactory;
private static final List<Integer> SUPPORTED_VERSIONS = Collections.unmodifiableList(Collections.singletonList(1));
@@ -74,9 +73,9 @@ public class SshAuthenticationService extends Service {
@Override
public void onCreate() {
super.onCreate();
mApiPermissionHelper = new ApiPermissionHelper(this, new ApiDataAccessObject(this));
mApiPermissionHelper = new ApiPermissionHelper(this, ApiAppDao.getInstance(this));
mKeyRepository = KeyRepository.create(this);
mApiDao = new ApiDataAccessObject(this);
mApiAppDao = ApiAppDao.getInstance(this);
mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext());
}
@@ -144,23 +143,18 @@ public class SshAuthenticationService extends Service {
AuthenticationData.Builder authData = AuthenticationData.builder();
authData.setAuthenticationMasterKeyId(masterKeyId);
CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId);
long authSubKeyId;
int authSubKeyAlgorithm;
String authSubKeyCurveOid = null;
try {
// get first usable subkey capable of authentication
authSubKeyId = cachedPublicKeyRing.getSecretAuthenticationId();
authSubKeyId = mKeyRepository.getSecretAuthenticationId(masterKeyId);
// needed for encoding the resulting signature
authSubKeyAlgorithm = getPublicKey(masterKeyId).getAlgorithm();
if (authSubKeyAlgorithm == PublicKeyAlgorithmTags.ECDSA) {
authSubKeyCurveOid = getPublicKey(masterKeyId).getCurveOid();
}
} catch (PgpKeyNotFoundException e) {
return createExceptionErrorResult(SshAuthenticationApiError.NO_AUTH_KEY,
"authentication key for master key id not found in keychain", e);
} catch (KeyRepository.NotFoundException e) {
} catch (NotFoundException e) {
return createExceptionErrorResult(SshAuthenticationApiError.NO_SUCH_KEY,
"Key for master key id not found", e);
}
@@ -272,7 +266,7 @@ public class SshAuthenticationService extends Service {
try {
description = getDescription(masterKeyId);
} catch (PgpKeyNotFoundException e) {
} catch (NotFoundException e) {
return createExceptionErrorResult(SshAuthenticationApiError.NO_SUCH_KEY,
"Could not create description", e);
}
@@ -372,21 +366,21 @@ public class SshAuthenticationService extends Service {
return new SshPublicKeyResponse(sshPublicKeyBlob).toIntent();
}
private CanonicalizedPublicKey getPublicKey(long masterKeyId)
throws PgpKeyNotFoundException, KeyRepository.NotFoundException {
private CanonicalizedPublicKey getPublicKey(long masterKeyId) throws NotFoundException {
KeyRepository keyRepository = KeyRepository.create(getApplicationContext());
long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId)
.getAuthenticationId();
return keyRepository.getCanonicalizedPublicKeyRing(masterKeyId)
.getPublicKey(authSubKeyId);
UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(masterKeyId);
if (unifiedKeyInfo == null) {
throw new NotFoundException();
}
return keyRepository.getCanonicalizedPublicKeyRing(masterKeyId).getPublicKey(unifiedKeyInfo.has_auth_key_int());
}
private String getDescription(long masterKeyId) throws PgpKeyNotFoundException {
CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId);
private String getDescription(long masterKeyId) throws NotFoundException {
UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(masterKeyId);
String description = "";
long authSubKeyId = cachedPublicKeyRing.getSecretAuthenticationId();
description += cachedPublicKeyRing.getPrimaryUserId();
long authSubKeyId = mKeyRepository.getSecretAuthenticationId(masterKeyId);
description += unifiedKeyInfo.user_id();
description += " (" + Long.toHexString(authSubKeyId) + ")";
return description;
@@ -394,7 +388,7 @@ public class SshAuthenticationService extends Service {
private HashSet<Long> getAllowedKeyIds() {
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
return mApiDao.getAllowedKeyIdsForApp(KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
return mApiAppDao.getAllowedKeyIdsForApp(currentPkg);
}
/**

View File

@@ -26,7 +26,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.view.Menu;
@@ -36,24 +35,26 @@ import android.widget.TextView;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.ApiApp;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
import timber.log.Timber;
public class AppSettingsActivity extends BaseActivity {
private Uri mAppUri;
public static final String EXTRA_PACKAGE_NAME = "package_name";
private String packageName;
private TextView mAppNameView;
private ImageView mAppIconView;
// model
AppSettings mAppSettings;
ApiApp mApiApp;
private ApiAppDao apiAppDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -68,15 +69,16 @@ public class AppSettingsActivity extends BaseActivity {
setTitle(null);
Intent intent = getIntent();
mAppUri = intent.getData();
if (mAppUri == null) {
Timber.e("Intent data missing. Should be Uri of app!");
packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
if (packageName == null) {
Timber.e("Required extra package_name missing!");
finish();
return;
}
Timber.d("uri: %s", mAppUri);
loadData(savedInstanceState, mAppUri);
apiAppDao = ApiAppDao.getInstance(this);
loadData(savedInstanceState);
}
private void save() {
@@ -138,7 +140,7 @@ public class AppSettingsActivity extends BaseActivity {
// advanced info: package certificate SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(mAppSettings.getPackageCertificate());
md.update(mApiApp.package_signature());
byte[] digest = md.digest();
certificate = new String(Hex.encode(digest));
} catch (NoSuchAlgorithmException e) {
@@ -146,7 +148,7 @@ public class AppSettingsActivity extends BaseActivity {
}
AdvancedAppSettingsDialogFragment dialogFragment =
AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate);
AdvancedAppSettingsDialogFragment.newInstance(mApiApp.package_name(), certificate);
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
}
@@ -155,7 +157,7 @@ public class AppSettingsActivity extends BaseActivity {
Intent i;
PackageManager manager = getPackageManager();
try {
i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName());
i = manager.getLaunchIntentForPackage(mApiApp.package_name());
if (i == null)
throw new PackageManager.NameNotFoundException();
// start like the Android launcher would do
@@ -167,31 +169,29 @@ public class AppSettingsActivity extends BaseActivity {
}
}
private void loadData(Bundle savedInstanceState, Uri appUri) {
mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri);
private void loadData(Bundle savedInstanceState) {
mApiApp = apiAppDao.getApiApp(packageName);
// get application name and icon from package manager
String appName;
Drawable appIcon = null;
PackageManager pm = getApplicationContext().getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0);
ApplicationInfo ai = pm.getApplicationInfo(mApiApp.package_name(), 0);
appName = (String) pm.getApplicationLabel(ai);
appIcon = pm.getApplicationIcon(ai);
} catch (PackageManager.NameNotFoundException e) {
// fallback
appName = mAppSettings.getPackageName();
appName = mApiApp.package_name();
}
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
Timber.d("allowedKeysUri: " + allowedKeysUri);
startListFragments(savedInstanceState, allowedKeysUri);
startListFragments(savedInstanceState);
}
private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) {
private void startListFragments(Bundle savedInstanceState) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
@@ -199,7 +199,8 @@ public class AppSettingsActivity extends BaseActivity {
return;
}
AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri);
// Create an instance of the fragments
AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(packageName);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
@@ -210,9 +211,7 @@ public class AppSettingsActivity extends BaseActivity {
}
private void revokeAccess() {
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
throw new RuntimeException();
}
apiAppDao.deleteApiApp(packageName);
finish();
}

View File

@@ -18,48 +18,36 @@
package org.sufficientlysecure.keychain.remote.ui;
import java.util.List;
import java.util.Set;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.support.v7.widget.LinearLayoutManager;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
import timber.log.Timber;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.daos.ApiAppDao;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter;
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String ARG_DATA_URI = "uri";
public class AppSettingsAllowedKeysListFragment extends RecyclerFragment<KeyChoiceAdapter> {
private static final String ARG_PACKAGE_NAME = "package_name";
private KeySelectableAdapter mAdapter;
private ApiDataAccessObject mApiDao;
private KeyChoiceAdapter keyChoiceAdapter;
private ApiAppDao apiAppDao;
private Uri mDataUri;
private String packageName;
/**
* Creates new instance of this fragment
*/
public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) {
public static AppSettingsAllowedKeysListFragment newInstance(String packageName) {
AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
args.putString(ARG_PACKAGE_NAME, packageName);
frag.setArguments(args);
return frag;
@@ -69,112 +57,48 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mApiDao = new ApiDataAccessObject(getActivity());
apiAppDao = ApiAppDao.getInstance(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = super.onCreateView(inflater, container, savedInstanceState);
ListView lv = layout.findViewById(android.R.id.list);
ViewGroup parent = (ViewGroup) lv.getParent();
/*
* http://stackoverflow.com/a/15880684
* Remove ListView and add FixedListView in its place.
* This is done here programatically to be still able to use the progressBar of ListFragment.
*
* We want FixedListView to be able to put this ListFragment inside a ScrollView
*/
int lvIndex = parent.indexOfChild(lv);
parent.removeViewAt(lvIndex);
FixedListView newLv = new FixedListView(getActivity());
newLv.setId(android.R.id.list);
parent.addView(newLv, lvIndex, lv.getLayoutParams());
return layout;
}
/**
* Define Adapter and Loader on create of Activity
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
packageName = getArguments().getString(ARG_PACKAGE_NAME);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(getString(R.string.list_empty));
Set<Long> checked = mApiDao.getAllowedKeyIdsForApp(mDataUri);
mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
setListAdapter(mAdapter);
getListView().setOnItemClickListener(mAdapter);
getRecyclerView().setLayoutManager(new LinearLayoutManager(requireContext()));
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
hideList(false);
KeyRepository keyRepository = KeyRepository.create(requireContext());
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
LiveData<List<UnifiedKeyInfo>> liveData =
viewModel.getGenericLiveData(requireContext(), keyRepository::getAllUnifiedKeyInfoWithSecret);
liveData.observe(this, this::onLoadUnifiedKeyData);
}
/** Returns all selected master key ids. */
public Set<Long> getSelectedMasterKeyIds() {
return mAdapter.getSelectedMasterKeyIds();
}
/** Returns all selected user ids.
public String[] getSelectedUserIds() {
Vector<String> userIds = new Vector<>();
for (int i = 0; i < getListView().getCount(); ++i) {
if (getListView().isItemChecked(i)) {
userIds.add(mAdapter.getUserId(i));
}
}
// make empty array to not return null
String userIdArray[] = new String[0];
return userIds.toArray(userIdArray);
} */
public void saveAllowedKeys() {
try {
mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());
} catch (RemoteException | OperationApplicationException e) {
Timber.e(e, "Problem saving allowed key ids!");
}
Set<Long> longs = keyChoiceAdapter.getSelectionIds();
apiAppDao.saveAllowedKeyIdsForApp(packageName, longs);
}
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle data) {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
return new CursorLoader(getActivity(), baseUri, KeyAdapter.PROJECTION, where, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
public void onLoadUnifiedKeyData(List<UnifiedKeyInfo> data) {
if (keyChoiceAdapter == null) {
keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(requireContext(), data, null);
setAdapter(keyChoiceAdapter);
Set<Long> checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName);
keyChoiceAdapter.setSelectionByIds(checkedIds);
} else {
setListShownNoAnimation(true);
keyChoiceAdapter.setUnifiedKeyInfoItems(data);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
boolean animateShowList = !isResumed();
showList(animateShowList);
}
}

View File

@@ -1,107 +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.remote.ui;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.remote.AppSettings;
import timber.log.Timber;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class AppSettingsHeaderFragment extends Fragment {
// model
private AppSettings mAppSettings;
// view
private TextView mAppNameView;
private ImageView mAppIconView;
private TextView mPackageName;
private TextView mPackageCertificate;
public AppSettings getAppSettings() {
return mAppSettings;
}
public void setAppSettings(AppSettings appSettings) {
this.mAppSettings = appSettings;
updateView(appSettings);
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
mAppNameView = view.findViewById(R.id.api_app_settings_app_name);
mAppIconView = view.findViewById(R.id.api_app_settings_app_icon);
mPackageName = view.findViewById(R.id.api_app_settings_package_name);
mPackageCertificate = view.findViewById(R.id.api_app_settings_package_certificate);
return view;
}
private void updateView(AppSettings appSettings) {
// get application name and icon from package manager
String appName;
Drawable appIcon = null;
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0);
appName = (String) pm.getApplicationLabel(ai);
appIcon = pm.getApplicationIcon(ai);
} catch (NameNotFoundException e) {
// fallback
appName = appSettings.getPackageName();
}
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
// advanced info: package name
mPackageName.setText(appSettings.getPackageName());
// advanced info: package signature SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(appSettings.getPackageCertificate());
byte[] digest = md.digest();
String signature = new String(Hex.encode(digest));
mPackageCertificate.setText(signature);
} catch (NoSuchAlgorithmException e) {
Timber.e(e, "Should not happen!");
}
}
}

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