diff --git a/CHANGELOG b/CHANGELOG index 548dd1289..45ad2e98c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,25 @@ +2.5 +* fix decryption of symmetric pgp messages/files +* refactored edit key screen (thanks to Ash Hughes) +* new modern design for encrypt/decrypt screens +* OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup) + +2.4 +Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser. +* new unified key list +* colorized key fingerprint +* support for keyserver ports +* deactivate possibility to generate weak keys +* much more internal work on the API +* certify user ids +* keyserver query based on machine-readable output +* lock navigation drawer on tablets +* suggestions for emails on creation of keys +* search in public key lists +* and much more improvements and fixes… + 2.3.1 * hotfix for crash when upgrading from old versions diff --git a/OpenPGP-Keychain-API/example-app/src/main/AndroidManifest.xml b/OpenPGP-Keychain-API/example-app/src/main/AndroidManifest.xml index 74bc88708..d62c26495 100644 --- a/OpenPGP-Keychain-API/example-app/src/main/AndroidManifest.xml +++ b/OpenPGP-Keychain-API/example-app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="4" + android:versionName="3"> @@ -66,6 +67,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:text="ciphertext" + android:hint="ciphertext" android:textAppearance="@android:style/TextAppearance.Small" /> @@ -104,5 +106,18 @@ android:layout_height="wrap_content" android:text="Decrypt and Verify" /> + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpError.java b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpError.java index 4dd2cc641..b894a4609 100644 --- a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpError.java +++ b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpError.java @@ -19,12 +19,22 @@ package org.openintents.openpgp; import android.os.Parcel; import android.os.Parcelable; +/** + * Parcelable versioning has been copied from Dashclock Widget + * https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java + */ public class OpenPgpError implements Parcelable { - public static final int CLIENT_SIDE_ERROR = -1; + /** + * Since there might be a case where new versions of the client using the library getting + * old versions of the protocol (and thus old versions of this class), we need a versioning + * system for the parcels sent between the clients and the providers. + */ + public static final int PARCELABLE_VERSION = 1; + // possible values for errorId + public static final int CLIENT_SIDE_ERROR = -1; public static final int GENERIC_ERROR = 0; public static final int INCOMPATIBLE_API_VERSIONS = 1; - public static final int NO_OR_WRONG_PASSPHRASE = 2; public static final int NO_USER_IDS = 3; @@ -65,15 +75,39 @@ public class OpenPgpError implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { + /** + * NOTE: When adding fields in the process of updating this API, make sure to bump + * {@link #PARCELABLE_VERSION}. + */ + dest.writeInt(PARCELABLE_VERSION); + // Inject a placeholder that will store the parcel size from this point on + // (not including the size itself). + int sizePosition = dest.dataPosition(); + dest.writeInt(0); + int startPosition = dest.dataPosition(); + // version 1 dest.writeInt(errorId); dest.writeString(message); + // Go back and write the size + int parcelableSize = dest.dataPosition() - startPosition; + dest.setDataPosition(sizePosition); + dest.writeInt(parcelableSize); + dest.setDataPosition(startPosition + parcelableSize); } public static final Creator CREATOR = new Creator() { public OpenPgpError createFromParcel(final Parcel source) { + int parcelableVersion = source.readInt(); + int parcelableSize = source.readInt(); + int startPosition = source.dataPosition(); + OpenPgpError error = new OpenPgpError(); error.errorId = source.readInt(); error.message = source.readString(); + + // skip over all fields added in future versions of this parcel + source.setDataPosition(startPosition + parcelableSize); + return error; } diff --git a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpSignatureResult.java b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpSignatureResult.java index cb220cf6d..157dd1aad 100644 --- a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpSignatureResult.java +++ b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/OpenPgpSignatureResult.java @@ -19,7 +19,18 @@ package org.openintents.openpgp; import android.os.Parcel; import android.os.Parcelable; +/** + * Parcelable versioning has been copied from Dashclock Widget + * https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java + */ public class OpenPgpSignatureResult implements Parcelable { + /** + * Since there might be a case where new versions of the client using the library getting + * old versions of the protocol (and thus old versions of this class), we need a versioning + * system for the parcels sent between the clients and the providers. + */ + public static final int PARCELABLE_VERSION = 1; + // generic error on signature verification public static final int SIGNATURE_ERROR = 0; // successfully verified signature, with certified public key @@ -90,19 +101,43 @@ public class OpenPgpSignatureResult implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { + /** + * NOTE: When adding fields in the process of updating this API, make sure to bump + * {@link #PARCELABLE_VERSION}. + */ + dest.writeInt(PARCELABLE_VERSION); + // Inject a placeholder that will store the parcel size from this point on + // (not including the size itself). + int sizePosition = dest.dataPosition(); + dest.writeInt(0); + int startPosition = dest.dataPosition(); + // version 1 dest.writeInt(status); dest.writeByte((byte) (signatureOnly ? 1 : 0)); dest.writeString(userId); dest.writeLong(keyId); + // Go back and write the size + int parcelableSize = dest.dataPosition() - startPosition; + dest.setDataPosition(sizePosition); + dest.writeInt(parcelableSize); + dest.setDataPosition(startPosition + parcelableSize); } public static final Creator CREATOR = new Creator() { public OpenPgpSignatureResult createFromParcel(final Parcel source) { + int parcelableVersion = source.readInt(); + int parcelableSize = source.readInt(); + int startPosition = source.dataPosition(); + OpenPgpSignatureResult vr = new OpenPgpSignatureResult(); vr.status = source.readInt(); vr.signatureOnly = source.readByte() == 1; vr.userId = source.readString(); vr.keyId = source.readLong(); + + // skip over all fields added in future versions of this parcel + source.setDataPosition(startPosition + parcelableSize); + return vr; } diff --git a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpApi.java b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpApi.java index f768a1685..465a12002 100644 --- a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpApi.java +++ b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpApi.java @@ -32,7 +32,7 @@ public class OpenPgpApi { public static final String TAG = "OpenPgp API"; - public static final int API_VERSION = 2; + public static final int API_VERSION = 3; public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService"; /** @@ -126,6 +126,8 @@ public class OpenPgpApi { /* Intent extras */ public static final String EXTRA_API_VERSION = "api_version"; + public static final String EXTRA_ACCOUNT_NAME = "account_name"; + // SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY // request ASCII Armor for output // OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53) diff --git a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpServiceConnection.java b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpServiceConnection.java index c80656c52..0395a7bc5 100644 --- a/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpServiceConnection.java +++ b/OpenPGP-Keychain-API/libraries/openpgp-api-library/src/org/openintents/openpgp/util/OpenPgpServiceConnection.java @@ -25,34 +25,64 @@ import android.content.ServiceConnection; import android.os.IBinder; public class OpenPgpServiceConnection { + + // interface to create callbacks for onServiceConnected + public interface OnBound { + public void onBound(IOpenPgpService service); + } + private Context mApplicationContext; - private boolean mBound; private IOpenPgpService mService; private String mProviderPackageName; + private OnBound mOnBoundListener; + + /** + * Create new OpenPgpServiceConnection + * + * @param context + * @param providerPackageName specify package name of OpenPGP provider, + * e.g., "org.sufficientlysecure.keychain" + */ public OpenPgpServiceConnection(Context context, String providerPackageName) { this.mApplicationContext = context.getApplicationContext(); this.mProviderPackageName = providerPackageName; } + /** + * Create new OpenPgpServiceConnection + * + * @param context + * @param providerPackageName specify package name of OpenPGP provider, + * e.g., "org.sufficientlysecure.keychain" + * @param onBoundListener callback, executed when connection to service has been established + */ + public OpenPgpServiceConnection(Context context, String providerPackageName, + OnBound onBoundListener) { + this.mApplicationContext = context.getApplicationContext(); + this.mProviderPackageName = providerPackageName; + this.mOnBoundListener = onBoundListener; + } + public IOpenPgpService getService() { return mService; } public boolean isBound() { - return mBound; + return (mService != null); } private ServiceConnection mServiceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { mService = IOpenPgpService.Stub.asInterface(service); - mBound = true; + if (mOnBoundListener != null) { + mOnBoundListener.onBound(mService); + } } public void onServiceDisconnected(ComponentName name) { mService = null; - mBound = false; } }; @@ -63,7 +93,7 @@ public class OpenPgpServiceConnection { */ public boolean bindToService() { // if not already bound... - if (mService == null && !mBound) { + if (mService == null) { try { Intent serviceIntent = new Intent(); serviceIntent.setAction(IOpenPgpService.class.getName()); diff --git a/OpenPGP-Keychain/build.gradle b/OpenPGP-Keychain/build.gradle index 04ee46715..ef9bcfda5 100644 --- a/OpenPGP-Keychain/build.gradle +++ b/OpenPGP-Keychain/build.gradle @@ -1,5 +1,12 @@ apply plugin: 'android' +sourceSets { + testLocal { + java.srcDir file('src/test/java') + resources.srcDir file('src/test/resources') + } +} + dependencies { compile 'com.android.support:support-v4:19.0.1' compile 'com.android.support:appcompat-v7:19.0.1' @@ -15,6 +22,25 @@ dependencies { compile project(':libraries:spongycastle:pkix') compile project(':libraries:spongycastle:prov') compile project(':libraries:Android-AppMsg:library') + + // Dependencies for the `testLocal` task, make sure to list all your global dependencies here as well + testLocalCompile 'junit:junit:4.11' + testLocalCompile 'org.robolectric:robolectric:2.1.+' + testLocalCompile 'com.google.android:android:4.1.1.4' + testLocalCompile 'com.android.support:support-v4:19.0.1' + testLocalCompile 'com.android.support:appcompat-v7:19.0.1' + testLocalCompile project(':OpenPGP-Keychain-API:libraries:openpgp-api-library') + testLocalCompile project(':OpenPGP-Keychain-API:libraries:openkeychain-api-library') + testLocalCompile project(':libraries:HtmlTextView') + testLocalCompile project(':libraries:StickyListHeaders:library') + testLocalCompile project(':libraries:AndroidBootstrap') + testLocalCompile project(':libraries:zxing') + testLocalCompile project(':libraries:zxing-android-integration') + testLocalCompile project(':libraries:spongycastle:core') + testLocalCompile project(':libraries:spongycastle:pg') + testLocalCompile project(':libraries:spongycastle:pkix') + testLocalCompile project(':libraries:spongycastle:prov') + testLocalCompile project(':libraries:Android-AppMsg:library') } android { @@ -61,3 +87,19 @@ android { htmlOutput file("lint-report.html") } } + +task localTest(type: Test, dependsOn: assemble) { + testClassesDir = sourceSets.testLocal.output.classesDir + + android.sourceSets.main.java.srcDirs.each { dir -> + def buildDir = dir.getAbsolutePath().split('/') + buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/') + + sourceSets.testLocal.compileClasspath += files(buildDir) + sourceSets.testLocal.runtimeClasspath += files(buildDir) + } + + classpath = sourceSets.testLocal.runtimeClasspath +} + +//check.dependsOn localTest diff --git a/OpenPGP-Keychain/src/main/AndroidManifest.xml b/OpenPGP-Keychain/src/main/AndroidManifest.xml index bd45def24..cf494ba08 100644 --- a/OpenPGP-Keychain/src/main/AndroidManifest.xml +++ b/OpenPGP-Keychain/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="25000" + android:versionName="2.5"> - + android:launchMode="singleTop"> - + android:launchMode="singleTop"> - + - - + + - - + + - + - - + + - - + + @@ -245,7 +243,7 @@ + android:label="@string/title_preferences"> @@ -383,44 +381,49 @@ android:exported="false" android:process=":passphrase_cache" /> - - + android:label="@string/app_name" + android:launchMode="singleTop" + android:process=":remote_api" /> + + + + - - diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 011cd9663..5b9f53b09 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -19,6 +19,11 @@ package org.sufficientlysecure.keychain; import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.sufficientlysecure.keychain.remote.ui.AppsListActivity; +import org.sufficientlysecure.keychain.ui.DecryptActivity; +import org.sufficientlysecure.keychain.ui.EncryptActivity; +import org.sufficientlysecure.keychain.ui.ImportKeysActivity; +import org.sufficientlysecure.keychain.ui.KeyListActivity; public final class Constants { @@ -42,7 +47,7 @@ public final class Constants { public static final class Path { public static final String APP_DIR = Environment.getExternalStorageDirectory() - + "/OpenPGP-Keychain"; + + "/OpenKeychain"; public static final String APP_DIR_FILE_SEC = APP_DIR + "/secexport.asc"; public static final String APP_DIR_FILE_PUB = APP_DIR + "/pubexport.asc"; } @@ -50,10 +55,10 @@ public final class Constants { public static final class Pref { public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm"; public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm"; - public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour"; + public static final String DEFAULT_ASCII_ARMOR = "defaultAsciiArmor"; public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression"; public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression"; - public static final String PASS_PHRASE_CACHE_TTL = "passphraseCacheTtl"; + public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl"; public static final String LANGUAGE = "language"; public static final String FORCE_V3_SIGNATURES = "forceV3Signatures"; public static final String KEY_SERVERS = "keyServers"; @@ -63,4 +68,18 @@ public final class Constants { public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu"; } + public static final class DrawerItems { + public static final Class KEY_LIST = KeyListActivity.class; + public static final Class ENCRYPT = EncryptActivity.class; + public static final Class DECRYPT = DecryptActivity.class; + public static final Class IMPORT_KEYS = ImportKeysActivity.class; + public static final Class REGISTERED_APPS_LIST = AppsListActivity.class; + public static final Class[] ARRAY = new Class[]{ + KEY_LIST, + ENCRYPT, + DECRYPT, + IMPORT_KEYS, + REGISTERED_APPS_LIST + }; + } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java index 1d79edd43..784ec340e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java @@ -119,6 +119,7 @@ public final class Id { public static final int secret_key = 0x21070002; public static final int user_id = 0x21070003; public static final int key = 0x21070004; + public static final int public_secret_key = 0x21070005; } public static final class choice { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 98b827542..9b80e76f3 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -17,16 +17,17 @@ package org.sufficientlysecure.keychain; -import java.io.File; -import java.security.Provider; -import java.security.Security; +import android.app.Application; +import android.os.Environment; import org.spongycastle.jce.provider.BouncyCastleProvider; + import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; -import android.app.Application; -import android.os.Environment; +import java.io.File; +import java.security.Provider; +import java.security.Security; public class KeychainApplication extends Application { @@ -40,14 +41,14 @@ public class KeychainApplication extends Application { /* * Sets Bouncy (Spongy) Castle as preferred security provider - * + * * insertProviderAt() position starts from 1 */ Security.insertProviderAt(new BouncyCastleProvider(), 1); /* * apply RNG fixes - * + * * among other things, executes Security.insertProviderAt(new * LinuxPRNGSecureRandomProvider(), 1) for Android <= SDK 17 */ diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java index 3164de7d1..1cac5762d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java @@ -17,20 +17,20 @@ package org.sufficientlysecure.keychain.compatibility; -import java.lang.reflect.Method; - import android.content.Context; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; +import java.lang.reflect.Method; + public class ClipboardReflection { private static final String clipboardLabel = "Keychain"; /** * Wrapper around ClipboardManager based on Android version using Reflection API - * + * * @param context * @param text */ @@ -57,7 +57,7 @@ public class ClipboardReflection { /** * Wrapper around ClipboardManager based on Android version using Reflection API - * + * * @param context */ public static CharSequence getClipboardText(Context context) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java index 91e50637e..a26df556d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java @@ -37,7 +37,6 @@ public class ActionBarHelper { * @param activity */ public static void setBackButton(ActionBarActivity activity) { - // set actionbar without home button if called from another app final ActionBar actionBar = activity.getSupportActionBar(); Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)=" + activity.getCallingPackage()); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java index 557d75dbf..810f22d8e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; @@ -47,23 +48,21 @@ public class ExportHelper { this.mActivity = activity; } - public void deleteKey(Uri dataUri, final int keyType, Handler deleteHandler) { - long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment()); - + public void deleteKey(Uri dataUri, Handler deleteHandler) { // Create a new Messenger for the communication back Messenger messenger = new Messenger(deleteHandler); + long masterKeyId = ProviderHelper.getMasterKeyId(mActivity, dataUri); DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - new long[]{keyRingRowId}, keyType); - + new long[]{ masterKeyId }); deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog"); } /** * Show dialog where to export keys */ - public void showExportKeysDialog(final long[] rowIds, final int keyType, - final String exportFilename) { + public void showExportKeysDialog(final long[] masterKeyIds, final String exportFilename, + final boolean showSecretCheckbox) { mExportFilename = exportFilename; // Message is received after file is selected @@ -74,7 +73,7 @@ public class ExportHelper { Bundle data = message.getData(); mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - exportKeys(rowIds, keyType); + exportKeys(masterKeyIds, data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED)); } } }; @@ -85,7 +84,7 @@ public class ExportHelper { DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { String title = null; - if (rowIds == null) { + if (masterKeyIds == null) { // export all keys title = mActivity.getString(R.string.title_export_keys); } else { @@ -93,15 +92,12 @@ public class ExportHelper { title = mActivity.getString(R.string.title_export_key); } - String message = null; - if (keyType == Id.type.public_key) { - message = mActivity.getString(R.string.specify_file_to_export_to); - } else { - message = mActivity.getString(R.string.specify_file_to_export_secret_keys_to); - } + String message = mActivity.getString(R.string.specify_file_to_export_to); + String checkMsg = showSecretCheckbox ? + mActivity.getString(R.string.also_export_secret_keys) : null; mFileDialog = FileDialogFragment.newInstance(messenger, title, message, - exportFilename, null); + exportFilename, checkMsg); mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog"); } @@ -111,7 +107,7 @@ public class ExportHelper { /** * Export keys */ - public void exportKeys(long[] rowIds, int keyType) { + public void exportKeys(long[] masterKeyIds, boolean exportSecret) { Log.d(Constants.TAG, "exportKeys started"); // Send all information needed to service to export key in other thread @@ -123,17 +119,17 @@ public class ExportHelper { Bundle data = new Bundle(); data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); - data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); + data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret); - if (rowIds == null) { + if (masterKeyIds == null) { data.putBoolean(KeychainIntentService.EXPORT_ALL, true); } else { - data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_ROW_ID, rowIds); + data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds); } intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after exporting is done in ApgService + // Message is received after exporting is done in KeychainIntentService KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity, mActivity.getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL, @@ -145,7 +141,7 @@ public class ExportHelper { } }) { public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java index 736bff02d..b31a889f0 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java @@ -17,18 +17,14 @@ package org.sufficientlysecure.keychain.helper; -import android.graphics.Color; import android.os.Bundle; -import android.text.Spannable; import android.text.SpannableStringBuilder; -import android.text.style.ForegroundColorSpan; +import android.text.Spanned; +import android.text.style.StrikethroughSpan; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; -import java.security.DigestException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.Iterator; import java.util.Set; @@ -65,81 +61,10 @@ public class OtherHelper { } } - public static SpannableStringBuilder colorizeFingerprint(String fingerprint) { - SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint); - try { - // for each 4 characters of the fingerprint + 1 space - for (int i = 0; i < fingerprint.length(); i += 5) { - int spanEnd = Math.min(i + 4, fingerprint.length()); - String fourChars = fingerprint.substring(i, spanEnd); - - int raw = Integer.parseInt(fourChars, 16); - byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)}; - int[] color = OtherHelper.getRgbForData(bytes); - int r = color[0]; - int g = color[1]; - int b = color[2]; - - // we cannot change black by multiplication, so adjust it to an almost-black grey, - // which will then be brightened to the minimal brightness level - if (r == 0 && g == 0 && b == 0) { - r = 1; - g = 1; - b = 1; - } - - // Convert rgb to brightness - double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; - - // If a color is too dark to be seen on black, - // then brighten it up to a minimal brightness. - if (brightness < 80) { - double factor = 80.0 / brightness; - r = Math.min(255, (int) (r * factor)); - g = Math.min(255, (int) (g * factor)); - b = Math.min(255, (int) (b * factor)); - - // If it is too light, then darken it to a respective maximal brightness. - } else if (brightness > 180) { - double factor = 180.0 / brightness; - r = (int) (r * factor); - g = (int) (g * factor); - b = (int) (b * factor); - } - - // Create a foreground color with the 3 digest integers as RGB - // and then converting that int to hex to use as a color - sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)), - i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE); - } - } catch (Exception e) { - Log.e(Constants.TAG, "Colorization failed", e); - // if anything goes wrong, then just display the fingerprint without colour, - // instead of partially correct colour or wrong colours - return new SpannableStringBuilder(fingerprint); - } - + public static SpannableStringBuilder strikeOutText(CharSequence text) { + SpannableStringBuilder sb = new SpannableStringBuilder(text); + sb.setSpan(new StrikethroughSpan(), 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); return sb; } - /** - * Converts the given bytes to a unique RGB color using SHA1 algorithm - * - * @param bytes - * @return an integer array containing 3 numeric color representations (Red, Green, Black) - * @throws NoSuchAlgorithmException - * @throws DigestException - */ - public static int[] getRgbForData(byte[] bytes) throws NoSuchAlgorithmException, DigestException { - MessageDigest md = MessageDigest.getInstance("SHA1"); - - md.update(bytes); - byte[] digest = md.digest(); - - int[] result = {((int) digest[0] + 256) % 256, - ((int) digest[1] + 256) % 256, - ((int) digest[2] + 256) % 256}; - return result; - } - } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java index 515201b92..ca5555fea 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2012 Dominik Schürmann * Copyright (C) 2010 Thialfihar - * + * * 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 @@ -30,7 +30,7 @@ import java.util.Vector; * Singleton Implementation of a Preference Helper */ public class Preferences { - private static Preferences mPreferences; + private static Preferences sPreferences; private SharedPreferences mSharedPreferences; public static synchronized Preferences getPreferences(Context context) { @@ -38,10 +38,10 @@ public class Preferences { } public static synchronized Preferences getPreferences(Context context, boolean forceNew) { - if (mPreferences == null || forceNew) { - mPreferences = new Preferences(context); + if (sPreferences == null || forceNew) { + sPreferences = new Preferences(context); } - return mPreferences; + return sPreferences; } private Preferences(Context context) { @@ -58,8 +58,8 @@ public class Preferences { editor.commit(); } - public long getPassPhraseCacheTtl() { - int ttl = mSharedPreferences.getInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, 180); + public long getPassphraseCacheTtl() { + int ttl = mSharedPreferences.getInt(Constants.Pref.PASSPHRASE_CACHE_TTL, 180); // fix the value if it was set to "never" in previous versions, which currently is not // supported if (ttl == 0) { @@ -68,9 +68,9 @@ public class Preferences { return (long) ttl; } - public void setPassPhraseCacheTtl(int value) { + public void setPassphraseCacheTtl(int value) { SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, value); + editor.putInt(Constants.Pref.PASSPHRASE_CACHE_TTL, value); editor.commit(); } @@ -118,13 +118,13 @@ public class Preferences { editor.commit(); } - public boolean getDefaultAsciiArmour() { - return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, false); + public boolean getDefaultAsciiArmor() { + return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, false); } - public void setDefaultAsciiArmour(boolean value) { + public void setDefaultAsciiArmor(boolean value) { SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, value); + editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, value); editor.commit(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java index fa9fcfccd..c6c62d649 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConversionHelper.java @@ -61,13 +61,32 @@ public class PgpConversionHelper { * @return */ public static ArrayList BytesToPGPSecretKeyList(byte[] keysBytes) { - PGPSecretKeyRing keyRing = (PGPSecretKeyRing) BytesToPGPKeyRing(keysBytes); + PGPObjectFactory factory = new PGPObjectFactory(keysBytes); + Object obj = null; ArrayList keys = new ArrayList(); - - @SuppressWarnings("unchecked") - Iterator itr = keyRing.getSecretKeys(); - while (itr.hasNext()) { - keys.add(itr.next()); + try { + while ((obj = factory.nextObject()) != null) { + PGPSecretKey secKey = null; + if (obj instanceof PGPSecretKey) { + secKey = (PGPSecretKey) obj; + if (secKey == null) { + Log.e(Constants.TAG, "No keys given!"); + } + keys.add(secKey); + } else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings + PGPSecretKeyRing keyRing = null; + keyRing = (PGPSecretKeyRing) obj; + if (keyRing == null) { + Log.e(Constants.TAG, "No keys given!"); + } + @SuppressWarnings("unchecked") + Iterator itr = keyRing.getSecretKeys(); + while (itr.hasNext()) { + keys.add(itr.next()); + } + } + } + } catch (IOException e) { } return keys; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 571729bc5..8a0bf99d7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -18,28 +18,58 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; + import org.openintents.openpgp.OpenPgpSignatureResult; import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.bcpg.SignatureSubpacketTags; -import org.spongycastle.openpgp.*; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPOnePassSignature; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPPBEEncryptedData; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.spongycastle.openpgp.operator.jcajce.*; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.security.SignatureException; +import java.util.HashMap; import java.util.Iterator; +import java.util.Set; /** * This class uses a Builder pattern! @@ -50,9 +80,9 @@ public class PgpDecryptVerify { private OutputStream mOutStream; private ProgressDialogUpdater mProgressDialogUpdater; - private boolean mAssumeSymmetric; + private boolean mAllowSymmetricDecryption; private String mPassphrase; - private long mEnforcedKeyId; + private Set mAllowedKeyIds; private PgpDecryptVerify(Builder builder) { // private Constructor can only be called from Builder @@ -61,9 +91,9 @@ public class PgpDecryptVerify { this.mOutStream = builder.mOutStream; this.mProgressDialogUpdater = builder.mProgressDialogUpdater; - this.mAssumeSymmetric = builder.mAssumeSymmetric; + this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption; this.mPassphrase = builder.mPassphrase; - this.mEnforcedKeyId = builder.mEnforcedKeyId; + this.mAllowedKeyIds = builder.mAllowedKeyIds; } public static class Builder { @@ -74,9 +104,9 @@ public class PgpDecryptVerify { // optional private ProgressDialogUpdater mProgressDialogUpdater = null; - private boolean mAssumeSymmetric = false; - private String mPassphrase = ""; - private long mEnforcedKeyId = 0; + private boolean mAllowSymmetricDecryption = true; + private String mPassphrase = null; + private Set mAllowedKeyIds = null; public Builder(Context context, InputData data, OutputStream outStream) { this.mContext = context; @@ -89,8 +119,8 @@ public class PgpDecryptVerify { return this; } - public Builder assumeSymmetric(boolean assumeSymmetric) { - this.mAssumeSymmetric = assumeSymmetric; + public Builder allowSymmetricDecryption(boolean allowSymmetricDecryption) { + this.mAllowSymmetricDecryption = allowSymmetricDecryption; return this; } @@ -100,14 +130,14 @@ public class PgpDecryptVerify { } /** - * Allow this key id alone for decryption. - * This means only ciphertexts encrypted for this private key can be decrypted. + * Allow these key ids alone for decryption. + * This means only ciphertexts encrypted for one of these private key can be decrypted. * - * @param enforcedKeyId + * @param allowedKeyIds * @return */ - public Builder enforcedKeyId(long enforcedKeyId) { - this.mEnforcedKeyId = enforcedKeyId; + public Builder allowedKeyIds(Set allowedKeyIds) { + this.mAllowedKeyIds = allowedKeyIds; return this; } @@ -128,35 +158,6 @@ public class PgpDecryptVerify { } } - public static boolean hasSymmetricEncryption(Context context, InputStream inputStream) - throws PgpGeneralException, IOException { - InputStream in = PGPUtil.getDecoderStream(inputStream); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - - // the first object might be a PGP marker packet. - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new PgpGeneralException(context.getString(R.string.error_invalid_data)); - } - - Iterator it = enc.getEncryptedDataObjects(); - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - return true; - } - } - - return false; - } - /** * Decrypts and/or verifies data based on parameters of class * @@ -221,25 +222,82 @@ public class PgpDecryptVerify { currentProgress += 5; - // TODO: currently we always only look at the first known key or symmetric encryption, - // there might be more... - if (mAssumeSymmetric) { - PGPPBEEncryptedData pbe = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - pbe = (PGPPBEEncryptedData) obj; - break; + PGPPublicKeyEncryptedData encryptedDataAsymmetric = null; + PGPPBEEncryptedData encryptedDataSymmetric = null; + PGPSecretKey secretKey = null; + Iterator it = enc.getEncryptedDataObjects(); + boolean symmetricPacketFound = false; + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPublicKeyEncryptedData) { + updateProgress(R.string.progress_finding_key, currentProgress, 100); + + PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; + long masterKeyId = ProviderHelper.getMasterKeyId(mContext, + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(encData.getKeyID())) + ); + PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRing(mContext, masterKeyId); + if (secretKeyRing == null) { + throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); } - } + secretKey = secretKeyRing.getSecretKey(encData.getKeyID()); + if (secretKey == null) { + throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); + } + // secret key exists in database - if (pbe == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_symmetric_encryption_packet)); - } + // allow only a specific key for decryption? + if (mAllowedKeyIds != null) { + Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID()); + Log.d(Constants.TAG, "allowedKeyIds: " + mAllowedKeyIds); + Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); + if (!mAllowedKeyIds.contains(masterKeyId)) { + throw new PgpGeneralException( + mContext.getString(R.string.error_no_secret_key_found)); + } + } + + encryptedDataAsymmetric = encData; + + // if no passphrase was explicitly set try to get it from the cache service + if (mPassphrase == null) { + // returns "" if key has no passphrase + mPassphrase = + PassphraseCacheService.getCachedPassphrase(mContext, masterKeyId); + + // if passphrase was not cached, return here + // indicating that a passphrase is missing! + if (mPassphrase == null) { + returnData.setKeyIdPassphraseNeeded(masterKeyId); + returnData.setStatus(PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED); + return returnData; + } + } + + // break out of while, only get first object here + // TODO???: There could be more pgp objects, which are not decrypted! + break; + } else if (mAllowSymmetricDecryption && obj instanceof PGPPBEEncryptedData) { + symmetricPacketFound = true; + + encryptedDataSymmetric = (PGPPBEEncryptedData) obj; + + // if no passphrase is given, return here + // indicating that a passphrase is missing! + if (mPassphrase == null) { + returnData.setStatus(PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED); + return returnData; + } + + // break out of while, only get first object here + // TODO???: There could be more pgp objects, which are not decrypted! + break; + } + } + + if (symmetricPacketFound) { updateProgress(R.string.progress_preparing_streams, currentProgress, 100); PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder() @@ -248,64 +306,11 @@ public class PgpDecryptVerify { digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( mPassphrase.toCharArray()); - clear = pbe.getDataStream(decryptorFactory); + clear = encryptedDataSymmetric.getDataStream(decryptorFactory); - encryptedData = pbe; + encryptedData = encryptedDataSymmetric; currentProgress += 5; } else { - updateProgress(R.string.progress_finding_key, currentProgress, 100); - - PGPPublicKeyEncryptedData pbe = null; - PGPSecretKey secretKey = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID()); - if (secretKey != null) { - // secret key exists in database - - // allow only a specific key for decryption? - if (mEnforcedKeyId != 0) { - // TODO: improve this code! get master key directly! - PGPSecretKeyRing secretKeyRing = - ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, encData.getKeyID()); - long masterKeyId = PgpKeyHelper.getMasterKey(secretKeyRing).getKeyID(); - Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID()); - Log.d(Constants.TAG, "enforcedKeyId: " + mEnforcedKeyId); - Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); - - if (mEnforcedKeyId != masterKeyId) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_secret_key_found)); - } - } - - pbe = encData; - - // if no passphrase was explicitly set try to get it from the cache service - if (mPassphrase == null) { - // returns "" if key has no passphrase - mPassphrase = - PassphraseCacheService.getCachedPassphrase(mContext, encData.getKeyID()); - - // if passphrase was not cached, return here - // indicating that a passphrase is missing! - if (mPassphrase == null) { - returnData.setKeyPassphraseNeeded(true); - return returnData; - } - } - - break; - } - - - } - } - if (secretKey == null) { throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); } @@ -331,9 +336,9 @@ public class PgpDecryptVerify { PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey); - clear = pbe.getDataStream(decryptorFactory); + clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); - encryptedData = pbe; + encryptedData = encryptedDataAsymmetric; currentProgress += 5; } @@ -363,7 +368,7 @@ public class PgpDecryptVerify { for (int i = 0; i < sigList.size(); ++i) { signature = sigList.get(i); signatureKey = ProviderHelper - .getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); + .getPGPPublicKeyRing(mContext, signature.getKeyID()).getPublicKey(); if (signatureKeyId == 0) { signatureKeyId = signature.getKeyID(); } @@ -373,10 +378,10 @@ public class PgpDecryptVerify { signatureIndex = i; signatureKeyId = signature.getKeyID(); String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId( + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId( mContext, signatureKeyId); if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); + userId = PgpKeyHelper.getMainUserId(signKeyRing.getPublicKey()); } signatureResult.setUserId(userId); break; @@ -388,7 +393,7 @@ public class PgpDecryptVerify { if (signature != null) { JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); signature.init(contentVerifierBuilderProvider, signatureKey); } else { @@ -546,25 +551,27 @@ public class PgpDecryptVerify { long signatureKeyId = 0; PGPPublicKey signatureKey = null; for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); - signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); + signatureKeyId = signature.getKeyID(); + + // find data about this subkey + HashMap data = ProviderHelper.getGenericData(mContext, + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(signature.getKeyID())), + new String[] { KeyRings.MASTER_KEY_ID, KeyRings.USER_ID }, + new int[] { ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_STRING }); + // any luck? otherwise, try next. + if(data.get(KeyRings.MASTER_KEY_ID) == null) { + signature = null; + // do NOT reset signatureKeyId, that one is shown when no known one is found! + continue; } - if (signatureKey == null) { - signature = null; - } else { - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, - signatureKeyId); - if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); - } - signatureResult.setUserId(userId); - break; - } + // this one can't fail now (yay database constraints) + signatureKey = ProviderHelper.getPGPPublicKeyRing(mContext, (Long) data.get(KeyRings.MASTER_KEY_ID)).getPublicKey(); + signatureResult.setUserId((String) data.get(KeyRings.USER_ID)); + + break; } signatureResult.setKeyId(signatureKeyId); @@ -621,11 +628,11 @@ public class PgpDecryptVerify { long signatureKeyId = signature.getKeyID(); boolean validKeyBinding = false; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context, + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(context, signatureKeyId); PGPPublicKey mKey = null; if (signKeyRing != null) { - mKey = PgpKeyHelper.getMasterKey(signKeyRing); + mKey = signKeyRing.getPublicKey(); } if (signature.getKeyID() != mKey.getKeyID()) { @@ -685,7 +692,8 @@ public class PgpDecryptVerify { } private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts, - PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { + PGPPublicKey masterPublicKey, + PGPPublicKey signingPublicKey) { boolean validPrimaryKeyBinding = false; JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java index d4a4f6075..ad240e834 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyResult.java @@ -19,27 +19,33 @@ package org.sufficientlysecure.keychain.pgp; import android.os.Parcel; import android.os.Parcelable; + import org.openintents.openpgp.OpenPgpSignatureResult; public class PgpDecryptVerifyResult implements Parcelable { - boolean mSymmetricPassphraseNeeded; - boolean mKeyPassphraseNeeded; + public static final int SUCCESS = 1; + public static final int KEY_PASSHRASE_NEEDED = 2; + public static final int SYMMETRIC_PASSHRASE_NEEDED = 3; + + int mStatus; + long mKeyIdPassphraseNeeded; + OpenPgpSignatureResult mSignatureResult; - public boolean isSymmetricPassphraseNeeded() { - return mSymmetricPassphraseNeeded; + public int getStatus() { + return mStatus; } - public void setSymmetricPassphraseNeeded(boolean symmetricPassphraseNeeded) { - this.mSymmetricPassphraseNeeded = symmetricPassphraseNeeded; + public void setStatus(int mStatus) { + this.mStatus = mStatus; } - public boolean isKeyPassphraseNeeded() { - return mKeyPassphraseNeeded; + public long getKeyIdPassphraseNeeded() { + return mKeyIdPassphraseNeeded; } - public void setKeyPassphraseNeeded(boolean keyPassphraseNeeded) { - this.mKeyPassphraseNeeded = keyPassphraseNeeded; + public void setKeyIdPassphraseNeeded(long mKeyIdPassphraseNeeded) { + this.mKeyIdPassphraseNeeded = mKeyIdPassphraseNeeded; } public OpenPgpSignatureResult getSignatureResult() { @@ -55,8 +61,8 @@ public class PgpDecryptVerifyResult implements Parcelable { } public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) { - this.mSymmetricPassphraseNeeded = b.mSymmetricPassphraseNeeded; - this.mKeyPassphraseNeeded = b.mKeyPassphraseNeeded; + this.mStatus = b.mStatus; + this.mKeyIdPassphraseNeeded = b.mKeyIdPassphraseNeeded; this.mSignatureResult = b.mSignatureResult; } @@ -66,16 +72,16 @@ public class PgpDecryptVerifyResult implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { - dest.writeByte((byte) (mSymmetricPassphraseNeeded ? 1 : 0)); - dest.writeByte((byte) (mKeyPassphraseNeeded ? 1 : 0)); + dest.writeInt(mStatus); + dest.writeLong(mKeyIdPassphraseNeeded); dest.writeParcelable(mSignatureResult, 0); } public static final Creator CREATOR = new Creator() { public PgpDecryptVerifyResult createFromParcel(final Parcel source) { PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult(); - vr.mSymmetricPassphraseNeeded = source.readByte() == 1; - vr.mKeyPassphraseNeeded = source.readByte() == 1; + vr.mStatus = source.readInt(); + vr.mKeyIdPassphraseNeeded = source.readLong(); vr.mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader()); return vr; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 2680d77af..f884b1776 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -20,7 +20,14 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; -import org.spongycastle.openpgp.*; + +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; @@ -43,10 +50,10 @@ public class PgpHelper { public static final Pattern PGP_MESSAGE = Pattern.compile( ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL); - public static final Pattern PGP_SIGNED_MESSAGE = Pattern - .compile( - ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", - Pattern.DOTALL); + public static final Pattern PGP_CLEARTEXT_SIGNATURE = Pattern + .compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----" + + "BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", + Pattern.DOTALL); public static final Pattern PGP_PUBLIC_KEY = Pattern.compile( ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", @@ -96,7 +103,7 @@ public class PgpHelper { if (obj instanceof PGPPublicKeyEncryptedData) { gotAsymmetricEncryption = true; PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj; - secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, pbe.getKeyID()); + secretKey = ProviderHelper.getPGPSecretKeyRing(context, pbe.getKeyID()).getSecretKey(); if (secretKey != null) { break; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index dbfa521e5..d03f3ccc2 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -20,8 +20,14 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; import android.os.Bundle; import android.os.Environment; + import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.openpgp.*; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; @@ -30,8 +36,12 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; -import org.sufficientlysecure.keychain.util.*; +import org.sufficientlysecure.keychain.util.HkpKeyServer; +import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException; +import org.sufficientlysecure.keychain.util.KeychainServiceListener; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -158,60 +168,69 @@ public class PgpImportExport { return returnData; } - public Bundle exportKeyRings(ArrayList keyRingRowIds, int keyType, + public Bundle exportKeyRings(ArrayList publicKeyRingMasterIds, + ArrayList secretKeyRingMasterIds, OutputStream outStream) throws PgpGeneralException, PGPException, IOException { Bundle returnData = new Bundle(); - int rowIdsSize = keyRingRowIds.size(); + int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size(); + int progress = 0; updateProgress( mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, - rowIdsSize), 0, 100); + masterKeyIdsSize), 0, 100); if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { throw new PgpGeneralException( mContext.getString(R.string.error_external_storage_not_ready)); } - // For each row id - for (int i = 0; i < rowIdsSize; ++i) { + // For each public masterKey id + for (long pubKeyMasterId : publicKeyRingMasterIds) { + progress++; // Create an output stream ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext)); - // If the keyType is secret get the PGPSecretKeyRing - // based on the row id and encode it to the output - if (keyType == Id.type.secret_key) { - updateProgress(i * 100 / rowIdsSize / 2, 100); - PGPSecretKeyRing secretKeyRing = - ProviderHelper.getPGPSecretKeyRingByRowId(mContext, keyRingRowIds.get(i)); + updateProgress(progress * 100 / masterKeyIdsSize, 100); + PGPPublicKeyRing publicKeyRing = + ProviderHelper.getPGPPublicKeyRing(mContext, pubKeyMasterId); - if (secretKeyRing != null) { - secretKeyRing.encode(arOutStream); - } - if (mKeychainServiceListener.hasServiceStopped()) { - arOutStream.close(); - return null; - } - } else { - updateProgress(i * 100 / rowIdsSize, 100); - PGPPublicKeyRing publicKeyRing = - ProviderHelper.getPGPPublicKeyRingByRowId(mContext, keyRingRowIds.get(i)); + if (publicKeyRing != null) { + publicKeyRing.encode(arOutStream); + } - if (publicKeyRing != null) { - publicKeyRing.encode(arOutStream); - } - - if (mKeychainServiceListener.hasServiceStopped()) { - arOutStream.close(); - return null; - } + if (mKeychainServiceListener.hasServiceStopped()) { + arOutStream.close(); + return null; } arOutStream.close(); } - returnData.putInt(KeychainIntentService.RESULT_EXPORT, rowIdsSize); + // For each secret masterKey id + for (long secretKeyMasterId : secretKeyRingMasterIds) { + progress++; + // Create an output stream + ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); + arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext)); + + updateProgress(progress * 100 / masterKeyIdsSize, 100); + PGPSecretKeyRing secretKeyRing = + ProviderHelper.getPGPSecretKeyRing(mContext, secretKeyMasterId); + + if (secretKeyRing != null) { + secretKeyRing.encode(arOutStream); + } + if (mKeychainServiceListener.hasServiceStopped()) { + arOutStream.close(); + return null; + } + + arOutStream.close(); + } + + returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize); updateProgress(R.string.progress_done, 100, 100); @@ -241,7 +260,6 @@ public class PgpImportExport { } if (save) { - ProviderHelper.saveKeyRing(mContext, secretKeyRing); // TODO: preserve certifications // (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?) PGPPublicKeyRing newPubRing = null; @@ -256,6 +274,7 @@ public class PgpImportExport { if (newPubRing != null) { ProviderHelper.saveKeyRing(mContext, newPubRing); } + ProviderHelper.saveKeyRing(mContext, secretKeyRing); // TODO: remove status returns, use exceptions! status = Id.return_value.ok; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index 586eb0776..4c786f555 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -18,8 +18,18 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; +import android.graphics.Color; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; + import org.spongycastle.bcpg.sig.KeyFlags; -import org.spongycastle.openpgp.*; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -27,7 +37,14 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; -import java.util.*; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,34 +60,6 @@ public class PgpKeyHelper { return key.getPublicKey().getCreationTime(); } - @SuppressWarnings("unchecked") - public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - - @SuppressWarnings("unchecked") - public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - @SuppressWarnings("unchecked") public static PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) { long cnt = 0; @@ -202,9 +191,8 @@ public class PgpKeyHelper { Calendar calendar = GregorianCalendar.getInstance(); calendar.setTime(creationDate); calendar.add(Calendar.DATE, key.getValidDays()); - Date expiryDate = calendar.getTime(); - return expiryDate; + return calendar.getTime(); } public static Date getExpiryDate(PGPSecretKey key) { @@ -212,8 +200,7 @@ public class PgpKeyHelper { } public static PGPPublicKey getEncryptPublicKey(Context context, long masterKeyId) { - PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(context, - masterKeyId); + PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRing(context, masterKeyId); if (keyRing == null) { Log.e(Constants.TAG, "keyRing is null!"); return null; @@ -227,8 +214,7 @@ public class PgpKeyHelper { } public static PGPSecretKey getCertificationKey(Context context, long masterKeyId) { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context, - masterKeyId); + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId); if (keyRing == null) { return null; } @@ -240,8 +226,7 @@ public class PgpKeyHelper { } public static PGPSecretKey getSigningKey(Context context, long masterKeyId) { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context, - masterKeyId); + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId); if (keyRing == null) { return null; } @@ -284,6 +269,33 @@ public class PgpKeyHelper { return userId; } + public static int getKeyUsage(PGPSecretKey key) { + return getKeyUsage(key.getPublicKey()); + } + + @SuppressWarnings("unchecked") + private static int getKeyUsage(PGPPublicKey key) { + int usage = 0; + if (key.getVersion() >= 4) { + for (PGPSignature sig : new IterableIterator(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + if (hashed != null) { + usage |= hashed.getKeyFlags(); + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + if (unhashed != null) { + usage |= unhashed.getKeyFlags(); + } + } + } + return usage; + } + @SuppressWarnings("unchecked") public static boolean isEncryptionKey(PGPPublicKey key) { if (!key.isEncryptionKey()) { @@ -390,6 +402,36 @@ public class PgpKeyHelper { return false; } + public static boolean isAuthenticationKey(PGPSecretKey key) { + return isAuthenticationKey(key.getPublicKey()); + } + + @SuppressWarnings("unchecked") + public static boolean isAuthenticationKey(PGPPublicKey key) { + if (key.getVersion() <= 3) { + return true; + } + + for (PGPSignature sig : new IterableIterator(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null && (hashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) { + return true; + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) { + return true; + } + } + + return false; + } + public static boolean isCertificationKey(PGPSecretKey key) { return isCertificationKey(key.getPublicKey()); } @@ -403,7 +445,7 @@ public class PgpKeyHelper { } public static String getAlgorithmInfo(int algorithm, int keySize) { - String algorithmStr = null; + String algorithmStr; switch (algorithm) { case PGPPublicKey.RSA_ENCRYPT: @@ -434,21 +476,6 @@ public class PgpKeyHelper { return algorithmStr; } - public static String getFingerPrint(Context context, long keyId) { - PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId); - // if it is no public key get it from your own keys... - if (key == null) { - PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId); - if (secretKey == null) { - Log.e(Constants.TAG, "Key could not be found!"); - return null; - } - key = secretKey.getPublicKey(); - } - - return convertFingerprintToHex(key.getFingerprint(), true); - } - /** * Converts fingerprint to hex (optional: with whitespaces after 4 characters) *

@@ -456,14 +483,10 @@ public class PgpKeyHelper { * better differentiate between numbers and letters when letters are lowercase. * * @param fingerprint - * @param split split into 4 character chunks * @return */ - public static String convertFingerprintToHex(byte[] fingerprint, boolean split) { + public static String convertFingerprintToHex(byte[] fingerprint) { String hexString = Hex.toHexString(fingerprint); - if (split) { - hexString = hexString.replaceAll("(.{4})(?!$)", "$1 "); - } return hexString; } @@ -479,9 +502,18 @@ public class PgpKeyHelper { * @return */ public static String convertKeyIdToHex(long keyId) { + long upper = keyId >> 32; + if (upper == 0) { + // this is a short key id + return convertKeyIdToHexShort(keyId); + } return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId); } + public static String convertKeyIdToHexShort(long keyId) { + return "0x" + convertKeyIdToHex32bit(keyId); + } + private static String convertKeyIdToHex32bit(long keyId) { String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US); while (hexString.length() < 8) { @@ -490,17 +522,90 @@ public class PgpKeyHelper { return hexString; } + + public static SpannableStringBuilder colorizeFingerprint(String fingerprint) { + // split by 4 characters + fingerprint = fingerprint.replaceAll("(.{4})(?!$)", "$1 "); + + // add line breaks to have a consistent "image" that can be recognized + char[] chars = fingerprint.toCharArray(); + chars[24] = '\n'; + fingerprint = String.valueOf(chars); + + SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint); + try { + // for each 4 characters of the fingerprint + 1 space + for (int i = 0; i < fingerprint.length(); i += 5) { + int spanEnd = Math.min(i + 4, fingerprint.length()); + String fourChars = fingerprint.substring(i, spanEnd); + + int raw = Integer.parseInt(fourChars, 16); + byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)}; + int[] color = getRgbForData(bytes); + int r = color[0]; + int g = color[1]; + int b = color[2]; + + // we cannot change black by multiplication, so adjust it to an almost-black grey, + // which will then be brightened to the minimal brightness level + if (r == 0 && g == 0 && b == 0) { + r = 1; + g = 1; + b = 1; + } + + // Convert rgb to brightness + double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; + + // If a color is too dark to be seen on black, + // then brighten it up to a minimal brightness. + if (brightness < 80) { + double factor = 80.0 / brightness; + r = Math.min(255, (int) (r * factor)); + g = Math.min(255, (int) (g * factor)); + b = Math.min(255, (int) (b * factor)); + + // If it is too light, then darken it to a respective maximal brightness. + } else if (brightness > 180) { + double factor = 180.0 / brightness; + r = (int) (r * factor); + g = (int) (g * factor); + b = (int) (b * factor); + } + + // Create a foreground color with the 3 digest integers as RGB + // and then converting that int to hex to use as a color + sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)), + i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + } catch (Exception e) { + Log.e(Constants.TAG, "Colorization failed", e); + // if anything goes wrong, then just display the fingerprint without colour, + // instead of partially correct colour or wrong colours + return new SpannableStringBuilder(fingerprint); + } + + return sb; + } + /** - * Used in HkpKeyServer to convert hex encoded key ids back to long. + * Converts the given bytes to a unique RGB color using SHA1 algorithm * - * @param hexString - * @return + * @param bytes + * @return an integer array containing 3 numeric color representations (Red, Green, Black) + * @throws java.security.NoSuchAlgorithmException + * @throws java.security.DigestException */ - public static long convertHexToKeyId(String hexString) { - int len = hexString.length(); - String s2 = hexString.substring(len - 8); - String s1 = hexString.substring(0, len - 8); - return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); + private static int[] getRgbForData(byte[] bytes) throws NoSuchAlgorithmException, DigestException { + MessageDigest md = MessageDigest.getInstance("SHA1"); + + md.update(bytes); + byte[] digest = md.digest(); + + int[] result = {((int) digest[0] + 256) % 256, + ((int) digest[1] + 256) % 256, + ((int) digest[2] + 256) % 256}; + return result; } /** diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 40a0b72ce..48b959738 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -17,33 +17,54 @@ package org.sufficientlysecure.keychain.pgp; -import android.content.Context; +import android.util.Pair; + import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.bcpg.sig.KeyFlags; -import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.spec.ElGamalParameterSpec; -import org.spongycastle.openpgp.*; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPKeyRingGenerator; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; import org.spongycastle.openpgp.operator.PGPDigestCalculator; -import org.spongycastle.openpgp.operator.jcajce.*; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.util.IterableIterator; -import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Primes; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import java.io.IOException; import java.math.BigInteger; -import java.security.*; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.SignatureException; import java.util.ArrayList; import java.util.Date; import java.util.GregorianCalendar; @@ -51,8 +72,16 @@ import java.util.Iterator; import java.util.List; import java.util.TimeZone; +/** This class is the single place where ALL operations that actually modify a PGP public or secret + * key take place. + * + * Note that no android specific stuff should be done here, ie no imports from com.android. + * + * All operations support progress reporting to a ProgressDialogUpdater passed on initialization. + * This indicator may be null. + * + */ public class PgpKeyOperation { - private Context mContext; private ProgressDialogUpdater mProgress; private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{ @@ -65,19 +94,18 @@ public class PgpKeyOperation { CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZIP}; - public PgpKeyOperation(Context context, ProgressDialogUpdater progress) { + public PgpKeyOperation(ProgressDialogUpdater progress) { super(); - this.mContext = context; this.mProgress = progress; } - public void updateProgress(int message, int current, int total) { + void updateProgress(int message, int current, int total) { if (mProgress != null) { mProgress.setProgress(message, current, total); } } - public void updateProgress(int current, int total) { + void updateProgress(int current, int total) { if (mProgress != null) { mProgress.setProgress(current, total); } @@ -90,11 +118,11 @@ public class PgpKeyOperation { * @param keySize * @param passphrase * @param isMasterKey - * @return + * @return A newly created PGPSecretKey * @throws NoSuchAlgorithmException * @throws PGPException * @throws NoSuchProviderException - * @throws PgpGeneralException + * @throws PgpGeneralMsgIdException * @throws InvalidAlgorithmParameterException */ @@ -102,18 +130,18 @@ public class PgpKeyOperation { public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase, boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, - PgpGeneralException, InvalidAlgorithmParameterException { + PgpGeneralMsgIdException, InvalidAlgorithmParameterException { if (keySize < 512) { - throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit)); + throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit); } if (passphrase == null) { passphrase = ""; } - int algorithm = 0; - KeyPairGenerator keyGen = null; + int algorithm; + KeyPairGenerator keyGen; switch (algorithmChoice) { case Id.choice.algorithm.dsa: { @@ -125,8 +153,7 @@ public class PgpKeyOperation { case Id.choice.algorithm.elgamal: { if (isMasterKey) { - throw new PgpGeneralException( - mContext.getString(R.string.error_master_key_must_not_be_el_gamal)); + throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal); } keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME); BigInteger p = Primes.getBestPrime(keySize); @@ -148,8 +175,7 @@ public class PgpKeyOperation { } default: { - throw new PgpGeneralException( - mContext.getString(R.string.error_unknown_algorithm_choice)); + throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice); } } @@ -165,194 +191,115 @@ public class PgpKeyOperation { PGPEncryptedData.CAST5, sha1Calc) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), - sha1Calc, isMasterKey, keyEncryptor); - - return secKey; + return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), + sha1Calc, isMasterKey, keyEncryptor); } - public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, - String newPassPhrase) throws IOException, PGPException, - NoSuchProviderException { + public PGPSecretKeyRing changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassphrase, + String newPassphrase) + throws IOException, PGPException, NoSuchProviderException { updateProgress(R.string.progress_building_key, 0, 100); - if (oldPassPhrase == null) { - oldPassPhrase = ""; + if (oldPassphrase == null) { + oldPassphrase = ""; } - if (newPassPhrase == null) { - newPassPhrase = ""; + if (newPassphrase == null) { + newPassphrase = ""; } PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword( keyRing, new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()), + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray()), new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey() - .getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray())); + .getKeyEncryptionAlgorithm()).build(newPassphrase.toCharArray())); - updateProgress(R.string.progress_saving_key_ring, 50, 100); - - ProviderHelper.saveKeyRing(mContext, newKeyRing); - - updateProgress(R.string.progress_done, 100, 100); + return newKeyRing; } - public void buildSecretKey(ArrayList userIds, ArrayList keys, - ArrayList keysUsages, ArrayList keysExpiryDates, - PGPPublicKey oldPublicKey, String oldPassPhrase, - String newPassPhrase) throws PgpGeneralException, NoSuchProviderException, - PGPException, NoSuchAlgorithmException, SignatureException, IOException { + private Pair buildNewSecretKey( + ArrayList userIds, ArrayList keys, + ArrayList keysExpiryDates, + ArrayList keysUsages, + String newPassphrase, String oldPassphrase) + throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { - Log.d(Constants.TAG, "userIds: " + userIds.toString()); + int usageId = keysUsages.get(0); + boolean canSign; + String mainUserId = userIds.get(0); - updateProgress(R.string.progress_building_key, 0, 100); + PGPSecretKey masterKey = keys.get(0); - if (oldPassPhrase == null) { - oldPassPhrase = ""; - } - if (newPassPhrase == null) { - newPassPhrase = ""; + // this removes all userIds and certifications previously attached to the masterPublicKey + PGPPublicKey masterPublicKey = masterKey.getPublicKey(); + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray()); + PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); + + updateProgress(R.string.progress_certifying_master_key, 20, 100); + + for (String userId : userIds) { + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + + sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + + PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); } - updateProgress(R.string.progress_preparing_master_key, 10, 100); + PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); - // prepare keyring generator with given master public and secret key - PGPKeyRingGenerator keyGen; - PGPPublicKey masterPublicKey; { + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - String mainUserId = userIds.get(0); + hashedPacketsGen.setKeyFlags(true, usageId); - // prepare the master key pair - PGPKeyPair masterKeyPair; { + hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); + hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); + hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); - PGPSecretKey masterKey = keys.get(0); - - // this removes all userIds and certifications previously attached to the masterPublicKey - PGPPublicKey tmpKey = masterKey.getPublicKey(); - masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), - tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); - - // already done by code above: - // PGPPublicKey masterPublicKey = masterKey.getPublicKey(); - // // Somehow, the PGPPublicKey already has an empty certification attached to it when the - // // keyRing is generated the first time, we remove that when it exists, before adding the - // new - // // ones - // PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey, - // ""); - // if (masterPublicKeyRmCert != null) { - // masterPublicKey = masterPublicKeyRmCert; - // } - - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()); - PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); - - updateProgress(R.string.progress_certifying_master_key, 20, 100); - - // re-add old certificates, or create new ones for new uids - for (String userId : userIds) { - // re-add certs for this uid, take a note if self-signed cert is in there - boolean foundSelfSign = false; - Iterator it = tmpKey.getSignaturesForID(userId); - if(it != null) for(PGPSignature sig : new IterableIterator(it)) { - if(sig.getKeyID() == masterPublicKey.getKeyID()) { - // already have a self sign? skip this other one, then. - // note: PGPKeyRingGenerator adds one cert for the main user id, which - // will lead to duplicates. unfortunately, if we add any other here - // first, that will change the main user id order... - if(foundSelfSign) - continue; - foundSelfSign = true; - } - Log.d(Constants.TAG, "adding old sig for " + userId + " from " - + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID())); - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, sig); - } - - // there was an old self-signed certificate for this uid - if(foundSelfSign) - continue; - - Log.d(Constants.TAG, "generating self-signed cert for " + userId); - - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - - sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); - - PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); - } - - masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); + if (keysExpiryDates.get(0) != null) { + GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + creationDate.setTime(masterPublicKey.getCreationTime()); + GregorianCalendar expiryDate = keysExpiryDates.get(0); + //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c + //here we purposefully ignore partial days in each date - long type has no fractional part! + long numDays = (expiryDate.getTimeInMillis() / 86400000) - + (creationDate.getTimeInMillis() / 86400000); + if (numDays <= 0) { + throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation); } - - PGPSignatureSubpacketGenerator hashedPacketsGen; - PGPSignatureSubpacketGenerator unhashedPacketsGen; { - - hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - - int usageId = keysUsages.get(0); - boolean canEncrypt = - (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); - - int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; - if (canEncrypt) { - keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; - } - hashedPacketsGen.setKeyFlags(true, keyFlags); - - hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); - hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); - hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); - - if (keysExpiryDates.get(0) != null) { - GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - creationDate.setTime(masterPublicKey.getCreationTime()); - GregorianCalendar expiryDate = keysExpiryDates.get(0); - //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c - //here we purposefully ignore partial days in each date - long type has no fractional part! - long numDays = - (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); - if (numDays <= 0) { - throw new PgpGeneralException( - mContext.getString(R.string.error_expiry_must_come_after_creation)); - } - hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); - } else { - //do this explicitly, although since we're rebuilding, - hashedPacketsGen.setKeyExpirationTime(false, 0); - //this happens anyway - } - } - - updateProgress(R.string.progress_building_master_key, 30, 100); - - // define hashing and signing algos - PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( - HashAlgorithmTags.SHA1); - PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder( - masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); - - // Build key encrypter based on passphrase - PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( - PGPEncryptedData.CAST5, sha1Calc) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - newPassPhrase.toCharArray()); - - keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, - masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(), - unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); - + hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); + } else { + hashedPacketsGen.setKeyExpirationTime(false, 0); + // do this explicitly, although since we're rebuilding, + // this happens anyway } + updateProgress(R.string.progress_building_master_key, 30, 100); + + // define hashing and signing algos + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( + HashAlgorithmTags.SHA1); + PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder( + masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); + + // Build key encrypter based on passphrase + PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( + PGPEncryptedData.CAST5, sha1Calc) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + newPassphrase.toCharArray()); + + PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, + masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(), + unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); + updateProgress(R.string.progress_adding_sub_keys, 40, 100); for (int i = 1; i < keys.size(); ++i) { @@ -361,27 +308,21 @@ public class PgpKeyOperation { PGPSecretKey subKey = keys.get(i); PGPPublicKey subPublicKey = subKey.getPublicKey(); - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - oldPassPhrase.toCharArray()); - PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor); + oldPassphrase.toCharArray()); + PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2); // TODO: now used without algorithm and creation time?! (APG 1) PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey); - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - int keyFlags = 0; - - int usageId = keysUsages.get(i); - boolean canSign = - (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); - boolean canEncrypt = - (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); + usageId = keysUsages.get(i); + canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this if (canSign) { Date todayDate = new Date(); //both sig times the same - keyFlags |= KeyFlags.SIGN_DATA; // cross-certify signing keys hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); @@ -396,10 +337,7 @@ public class PgpKeyOperation { subPublicKey); unhashedPacketsGen.setEmbeddedSignature(false, certification); } - if (canEncrypt) { - keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; - } - hashedPacketsGen.setKeyFlags(false, keyFlags); + hashedPacketsGen.setKeyFlags(false, usageId); if (keysExpiryDates.get(i) != null) { GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); @@ -407,17 +345,16 @@ public class PgpKeyOperation { GregorianCalendar expiryDate = keysExpiryDates.get(i); //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c //here we purposefully ignore partial days in each date - long type has no fractional part! - long numDays = - (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); + long numDays = (expiryDate.getTimeInMillis() / 86400000) - + (creationDate.getTimeInMillis() / 86400000); if (numDays <= 0) { - throw new PgpGeneralException - (mContext.getString(R.string.error_expiry_must_come_after_creation)); + throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation); } hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); } else { - //do this explicitly, although since we're rebuilding, hashedPacketsGen.setKeyExpirationTime(false, 0); - //this happens anyway + // do this explicitly, although since we're rebuilding, + // this happens anyway } keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate()); @@ -426,102 +363,407 @@ public class PgpKeyOperation { PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); - updateProgress(R.string.progress_re_adding_certs, 80, 100); + return new Pair(secretKeyRing, publicKeyRing); - // re-add certificates from old public key - // TODO: this only takes care of user id certificates, what about others? - PGPPublicKey pubkey = publicKeyRing.getPublicKey(); - for(String uid : new IterableIterator(pubkey.getUserIDs())) { - for(PGPSignature sig : new IterableIterator(oldPublicKey.getSignaturesForID(uid), true)) { - // but skip self certificates - if(sig.getKeyID() == pubkey.getKeyID()) - continue; - pubkey = PGPPublicKey.addCertification(pubkey, uid, sig); - } - } - publicKeyRing = PGPPublicKeyRing.insertPublicKey(publicKeyRing, pubkey); + } - updateProgress(R.string.progress_saving_key_ring, 90, 100); + public Pair buildSecretKey(PGPSecretKeyRing mKR, + PGPPublicKeyRing pKR, + SaveKeyringParcel saveParcel) + throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { - /* additional handy debug info - Log.d(Constants.TAG, " ------- in private key -------"); - for(String uid : new IterableIterator(secretKeyRing.getPublicKey().getUserIDs())) { - for(PGPSignature sig : new IterableIterator(secretKeyRing.getPublicKey().getSignaturesForID(uid))) { - Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); - } + updateProgress(R.string.progress_building_key, 0, 100); + PGPSecretKey masterKey = saveParcel.keys.get(0); + + if (saveParcel.oldPassphrase == null) { + saveParcel.oldPassphrase = ""; } - Log.d(Constants.TAG, " ------- in public key -------"); - for(String uid : new IterableIterator(publicKeyRing.getPublicKey().getUserIDs())) { - for(PGPSignature sig : new IterableIterator(publicKeyRing.getPublicKey().getSignaturesForID(uid))) { - Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); - } + if (saveParcel.newPassphrase == null) { + saveParcel.newPassphrase = ""; } + + if (mKR == null) { + return buildNewSecretKey(saveParcel.userIDs, saveParcel.keys, saveParcel.keysExpiryDates, + saveParcel.keysUsages, saveParcel.newPassphrase, saveParcel.oldPassphrase); //new Keyring + } + + /* + IDs - NB This might not need to happen later, if we change the way the primary ID is chosen + remove deleted ids + if the primary ID changed we need to: + remove all of the IDs from the keyring, saving their certifications + add them all in again, updating certs of IDs which have changed + else + remove changed IDs and add in with new certs + + if the master key changed, we need to remove the primary ID certification, so we can add + the new one when it is generated, and they don't conflict + + Keys + remove deleted keys + if a key is modified, re-sign it + do we need to remove and add in? + + Todo + identify more things which need to be preserved - e.g. trust levels? + user attributes */ - ProviderHelper.saveKeyRing(mContext, secretKeyRing); - ProviderHelper.saveKeyRing(mContext, publicKeyRing); + if (saveParcel.deletedKeys != null) { + for (PGPSecretKey dKey : saveParcel.deletedKeys) { + mKR = PGPSecretKeyRing.removeSecretKey(mKR, dKey); + } + } + + masterKey = mKR.getSecretKey(); + PGPPublicKey masterPublicKey = masterKey.getPublicKey(); + + int usageId = saveParcel.keysUsages.get(0); + boolean canSign; + String mainUserId = saveParcel.userIDs.get(0); + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(saveParcel.oldPassphrase.toCharArray()); + PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); + + updateProgress(R.string.progress_certifying_master_key, 20, 100); + + boolean anyIDChanged = false; + for (String delID : saveParcel.deletedIDs) { + anyIDChanged = true; + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, delID); + } + + int userIDIndex = 0; + + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + hashedPacketsGen.setKeyFlags(true, usageId); + + hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); + hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); + hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); + + if (saveParcel.keysExpiryDates.get(0) != null) { + GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + creationDate.setTime(masterPublicKey.getCreationTime()); + GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(0); + //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c + //here we purposefully ignore partial days in each date - long type has no fractional part! + long numDays = (expiryDate.getTimeInMillis() / 86400000) - + (creationDate.getTimeInMillis() / 86400000); + if (numDays <= 0) { + throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation); + } + hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); + } else { + hashedPacketsGen.setKeyExpirationTime(false, 0); + // do this explicitly, although since we're rebuilding, + // this happens anyway + } + + if (saveParcel.primaryIDChanged || + !saveParcel.originalIDs.get(0).equals(saveParcel.userIDs.get(0))) { + anyIDChanged = true; + ArrayList> sigList = new ArrayList>(); + for (String userId : saveParcel.userIDs) { + String origID = saveParcel.originalIDs.get(userIDIndex); + if (origID.equals(userId) && !saveParcel.newIDs[userIDIndex] && + !userId.equals(saveParcel.originalPrimaryID) && userIDIndex != 0) { + Iterator origSigs = masterPublicKey.getSignaturesForID(origID); + // TODO: make sure this iterator only has signatures we are interested in + while (origSigs.hasNext()) { + PGPSignature origSig = origSigs.next(); + sigList.add(new Pair(origID, origSig)); + } + } else { + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + + sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + if (userIDIndex == 0) { + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + sGen.setUnhashedSubpackets(unhashedPacketsGen.generate()); + } + PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); + sigList.add(new Pair(userId, certification)); + } + if (!saveParcel.newIDs[userIDIndex]) { + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID); + } + userIDIndex++; + } + for (Pair toAdd : sigList) { + masterPublicKey = + PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second); + } + } else { + for (String userId : saveParcel.userIDs) { + String origID = saveParcel.originalIDs.get(userIDIndex); + if (!origID.equals(userId) || saveParcel.newIDs[userIDIndex]) { + anyIDChanged = true; + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + + sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + if (userIDIndex == 0) { + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + sGen.setUnhashedSubpackets(unhashedPacketsGen.generate()); + } + PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); + if (!saveParcel.newIDs[userIDIndex]) { + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID); + } + masterPublicKey = + PGPPublicKey.addCertification(masterPublicKey, userId, certification); + } + userIDIndex++; + } + } + + ArrayList> sigList = new ArrayList>(); + if (saveParcel.moddedKeys[0]) { + userIDIndex = 0; + for (String userId : saveParcel.userIDs) { + String origID = saveParcel.originalIDs.get(userIDIndex); + if (!(origID.equals(saveParcel.originalPrimaryID) && !saveParcel.primaryIDChanged)) { + Iterator sigs = masterPublicKey.getSignaturesForID(userId); + // TODO: make sure this iterator only has signatures we are interested in + while (sigs.hasNext()) { + PGPSignature sig = sigs.next(); + sigList.add(new Pair(userId, sig)); + } + } + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, userId); + userIDIndex++; + } + anyIDChanged = true; + } + + //update the keyring with the new ID information + if (anyIDChanged) { + pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey); + mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR); + } + + PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); + + updateProgress(R.string.progress_building_master_key, 30, 100); + + // define hashing and signing algos + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( + HashAlgorithmTags.SHA1); + PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder( + masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); + + // Build key encryptor based on old passphrase, as some keys may be unchanged + PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( + PGPEncryptedData.CAST5, sha1Calc) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + saveParcel.oldPassphrase.toCharArray()); + + //this generates one more signature than necessary... + PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, + masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(), + unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); + + for (int i = 1; i < saveParcel.keys.size(); ++i) { + updateProgress(40 + 50 * i / saveParcel.keys.size(), 100); + if (saveParcel.moddedKeys[i]) { + PGPSecretKey subKey = saveParcel.keys.get(i); + PGPPublicKey subPublicKey = subKey.getPublicKey(); + + PBESecretKeyDecryptor keyDecryptor2; + if (saveParcel.newKeys[i]) { + keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + "".toCharArray()); + } else { + keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + saveParcel.oldPassphrase.toCharArray()); + } + PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2); + PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey); + + hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + usageId = saveParcel.keysUsages.get(i); + canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this + if (canSign) { + Date todayDate = new Date(); //both sig times the same + // cross-certify signing keys + hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time + PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); + subHashedPacketsGen.setSignatureCreationTime(false, todayDate); //set inner creation time + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + subPublicKey.getAlgorithm(), PGPUtil.SHA1) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); + sGen.setHashedSubpackets(subHashedPacketsGen.generate()); + PGPSignature certification = sGen.generateCertification(masterPublicKey, + subPublicKey); + unhashedPacketsGen.setEmbeddedSignature(false, certification); + } + hashedPacketsGen.setKeyFlags(false, usageId); + + if (saveParcel.keysExpiryDates.get(i) != null) { + GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + creationDate.setTime(subPublicKey.getCreationTime()); + GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(i); + // note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c + // here we purposefully ignore partial days in each date - long type has + // no fractional part! + long numDays = (expiryDate.getTimeInMillis() / 86400000) - + (creationDate.getTimeInMillis() / 86400000); + if (numDays <= 0) { + throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation); + } + hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); + } else { + hashedPacketsGen.setKeyExpirationTime(false, 0); + // do this explicitly, although since we're rebuilding, + // this happens anyway + } + + keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate()); + // certifications will be discarded if the key is changed, because I think, for a start, + // they will be invalid. Binding certs are regenerated anyway, and other certs which + // need to be kept are on IDs and attributes + // TODO: don't let revoked keys be edited, other than removed - changing one would + // result in the revocation being wrong? + } + } + + PGPSecretKeyRing updatedSecretKeyRing = keyGen.generateSecretKeyRing(); + //finally, update the keyrings + Iterator itr = updatedSecretKeyRing.getSecretKeys(); + while (itr.hasNext()) { + PGPSecretKey theNextKey = itr.next(); + if ((theNextKey.isMasterKey() && saveParcel.moddedKeys[0]) || !theNextKey.isMasterKey()) { + mKR = PGPSecretKeyRing.insertSecretKey(mKR, theNextKey); + pKR = PGPPublicKeyRing.insertPublicKey(pKR, theNextKey.getPublicKey()); + } + } + + //replace lost IDs + if (saveParcel.moddedKeys[0]) { + masterPublicKey = mKR.getPublicKey(); + for (Pair toAdd : sigList) { + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second); + } + pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey); + mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR); + } + + // Build key encryptor based on new passphrase + PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( + PGPEncryptedData.CAST5, sha1Calc) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + saveParcel.newPassphrase.toCharArray()); + + //update the passphrase + mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew); + + /* additional handy debug info + + Log.d(Constants.TAG, " ------- in private key -------"); + + for(String uid : new IterableIterator(secretKeyRing.getPublicKey().getUserIDs())) { + for(PGPSignature sig : new IterableIterator( + secretKeyRing.getPublicKey().getSignaturesForID(uid))) { + Log.d(Constants.TAG, "sig: " + + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); + } + + } + + Log.d(Constants.TAG, " ------- in public key -------"); + + for(String uid : new IterableIterator(publicKeyRing.getPublicKey().getUserIDs())) { + for(PGPSignature sig : new IterableIterator( + publicKeyRing.getPublicKey().getSignaturesForID(uid))) { + Log.d(Constants.TAG, "sig: " + + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); + } + } + + */ + + return new Pair(mKR, pKR); - updateProgress(R.string.progress_done, 100, 100); } /** * Certify the given pubkeyid with the given masterkeyid. * - * @param masterKeyId Certifying key, must be available as secret key - * @param pubKeyId ID of public key to certify + * @param certificationKey Certifying key + * @param publicKey public key to certify * @param userIds User IDs to certify, must not be null or empty * @param passphrase Passphrase of the secret key * @return A keyring with added certifications */ - public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List userIds, String passphrase) - throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException, - PGPException, SignatureException { - if (passphrase == null) { - throw new PgpGeneralException("Unable to obtain passphrase"); - } else { + public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey, + List userIds, String passphrase) + throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException, + PGPException, SignatureException { - // create a signatureGenerator from the supplied masterKeyId and passphrase - PGPSignatureGenerator signatureGenerator; { + // create a signatureGenerator from the supplied masterKeyId and passphrase + PGPSignatureGenerator signatureGenerator; { - PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); - if (certificationKey == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); - } - - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor); - if (signaturePrivateKey == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - - // TODO: SHA256 fixed? - JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( - certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey); + if (certificationKey == null) { + throw new PgpGeneralMsgIdException(R.string.error_signature_failed); } - { // supply signatureGenerator with a SubpacketVector - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - PGPSignatureSubpacketVector packetVector = spGen.generate(); - signatureGenerator.setHashedSubpackets(packetVector); + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor); + if (signaturePrivateKey == null) { + throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key); } - // fetch public key ring, add the certification and return it - PGPPublicKeyRing pubring = ProviderHelper - .getPGPPublicKeyRingByKeyId(mContext, pubKeyId); - PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId); - for(String userId : new IterableIterator(userIds.iterator())) { - PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey); - signedKey = PGPPublicKey.addCertification(signedKey, userId, sig); - } - pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey); + // TODO: SHA256 fixed? + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( + certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - return pubring; + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey); + } + + { // supply signatureGenerator with a SubpacketVector + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketVector packetVector = spGen.generate(); + signatureGenerator.setHashedSubpackets(packetVector); + } + + // fetch public key ring, add the certification and return it + for (String userId : new IterableIterator(userIds.iterator())) { + PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey); + publicKey = PGPPublicKey.addCertification(publicKey, userId, sig); + } + + return publicKey; + } + + /** Simple static subclass that stores two values. + * + * This is only used to return a pair of values in one function above. We specifically don't use + * com.android.Pair to keep this class free from android dependencies. + */ + public static class Pair { + public final K first; + public final V second; + public Pair(K first, V second) { + this.first = first; + this.second = second; } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index 737e9c75d..a864a165d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -18,11 +18,28 @@ package org.sufficientlysecure.keychain.pgp; import android.content.Context; + import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.BCPGOutputStream; -import org.spongycastle.openpgp.*; +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPLiteralDataGenerator; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPV3SignatureGenerator; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.jcajce.*; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; @@ -32,7 +49,11 @@ import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; @@ -50,7 +71,7 @@ public class PgpSignEncrypt { private boolean mEnableAsciiArmorOutput; private int mCompressionId; private long[] mEncryptionKeyIds; - private String mEncryptionPassphrase; + private String mSymmetricPassphrase; private int mSymmetricEncryptionAlgorithm; private long mSignatureKeyId; private int mSignatureHashAlgorithm; @@ -67,7 +88,7 @@ public class PgpSignEncrypt { this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput; this.mCompressionId = builder.mCompressionId; this.mEncryptionKeyIds = builder.mEncryptionKeyIds; - this.mEncryptionPassphrase = builder.mEncryptionPassphrase; + this.mSymmetricPassphrase = builder.mSymmetricPassphrase; this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm; this.mSignatureKeyId = builder.mSignatureKeyId; this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; @@ -85,8 +106,8 @@ public class PgpSignEncrypt { private ProgressDialogUpdater mProgress = null; private boolean mEnableAsciiArmorOutput = false; private int mCompressionId = Id.choice.compression.none; - private long[] mEncryptionKeyIds = new long[0]; - private String mEncryptionPassphrase = null; + private long[] mEncryptionKeyIds = null; + private String mSymmetricPassphrase = null; private int mSymmetricEncryptionAlgorithm = 0; private long mSignatureKeyId = Id.key.none; private int mSignatureHashAlgorithm = 0; @@ -119,8 +140,8 @@ public class PgpSignEncrypt { return this; } - public Builder encryptionPassphrase(String encryptionPassphrase) { - this.mEncryptionPassphrase = encryptionPassphrase; + public Builder symmetricPassphrase(String symmetricPassphrase) { + this.mSymmetricPassphrase = symmetricPassphrase; return this; } @@ -181,7 +202,8 @@ public class PgpSignEncrypt { NoSuchAlgorithmException, SignatureException { boolean enableSignature = mSignatureKeyId != Id.key.none; - boolean enableEncryption = (mEncryptionKeyIds.length != 0 || mEncryptionPassphrase != null); + boolean enableEncryption = ((mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) + || mSymmetricPassphrase != null); boolean enableCompression = (enableEncryption && mCompressionId != Id.choice.compression.none); Log.d(Constants.TAG, "enableSignature:" + enableSignature @@ -212,7 +234,7 @@ public class PgpSignEncrypt { PGPSecretKeyRing signingKeyRing = null; PGPPrivateKey signaturePrivateKey = null; if (enableSignature) { - signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId); + signingKeyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId); signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId); if (signingKey == null) { throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); @@ -246,12 +268,12 @@ public class PgpSignEncrypt { cPk = new PGPEncryptedDataGenerator(encryptorBuilder); - if (mEncryptionKeyIds.length == 0) { + if (mSymmetricPassphrase != null) { // Symmetric encryption Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption"); JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = - new JcePBEKeyEncryptionMethodGenerator(mEncryptionPassphrase.toCharArray()); + new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray()); cPk.addMethod(symmetricEncryptionGenerator); } else { // Asymmetric encryption @@ -284,7 +306,7 @@ public class PgpSignEncrypt { signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); signatureGenerator.init(signatureType, signaturePrivateKey); - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey()); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); spGen.setSignerUserID(false, userId); signatureGenerator.setHashedSubpackets(spGen.generate()); @@ -442,7 +464,7 @@ public class PgpSignEncrypt { } PGPSecretKeyRing signingKeyRing = - ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId); + ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId); PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId); if (signingKey == null) { throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); @@ -483,7 +505,7 @@ public class PgpSignEncrypt { signatureGenerator.init(type, signaturePrivateKey); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey()); spGen.setSignerUserID(false, userId); signatureGenerator.setHashedSubpackets(spGen.generate()); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java index 54601173d..5bb1665b6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpToX509.java @@ -18,7 +18,13 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.asn1.DERObjectIdentifier; -import org.spongycastle.asn1.x509.*; +import org.spongycastle.asn1.x509.AuthorityKeyIdentifier; +import org.spongycastle.asn1.x509.BasicConstraints; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.SubjectKeyIdentifier; +import org.spongycastle.asn1.x509.X509Extensions; +import org.spongycastle.asn1.x509.X509Name; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; @@ -29,13 +35,14 @@ import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; import java.io.IOException; import java.math.BigInteger; -import java.security.*; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.DateFormat; @@ -43,6 +50,11 @@ import java.util.Date; import java.util.Iterator; import java.util.Vector; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + public class PgpToX509 { public static final String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge"; public static final String DN_COMMON_PART_OU = "OpenPGP Keychain cert"; @@ -71,9 +83,10 @@ public class PgpToX509 { * @throws Exception * @author Bruno Harbulot */ - public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey, - X509Name subject, Date startDate, Date endDate, String subjAltNameURI) - throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, + public static X509Certificate createSelfSignedCert( + PublicKey pubKey, PrivateKey privKey, X509Name subject, Date startDate, Date endDate, + String subjAltNameURI) + throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, SignatureException, CertificateException, NoSuchProviderException { X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator(); @@ -170,10 +183,10 @@ public class PgpToX509 { /** * Creates a self-signed certificate from a PGP Secret Key. * - * @param pgpSecKey PGP Secret Key (from which one can extract the public and private keys and other - * attributes). - * @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks should be done - * before calling this method) + * @param pgpSecKey PGP Secret Key (from which one can extract the public and private + * keys and other attributes). + * @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks + * should be done before calling this method) * @param subjAltNameURI optional URI to embed in the subject alternative-name * @return self-signed certificate * @throws PGPException @@ -184,9 +197,9 @@ public class PgpToX509 { * @throws CertificateException * @author Bruno Harbulot */ - public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey, - PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException, - NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException, + public static X509Certificate createSelfSignedCert( + PGPSecretKey pgpSecKey, PGPPrivateKey pgpPrivKey, String subjAltNameURI) + throws PGPException, NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, CertificateException { // get public key from secret key PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java index bb80d27ee..418445367 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralException.java @@ -23,4 +23,7 @@ public class PgpGeneralException extends Exception { public PgpGeneralException(String message) { super(message); } + public PgpGeneralException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java new file mode 100644 index 000000000..caa7842db --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-2013 Dominik Schürmann + * Copyright (C) 2010 Thialfihar + * + * 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 org.sufficientlysecure.keychain.pgp.exception; + +import android.content.Context; + +public class PgpGeneralMsgIdException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + private final int mMessageId; + + public PgpGeneralMsgIdException(int messageId) { + super("msg[" + messageId + "]"); + mMessageId = messageId; + } + + public PgpGeneralException getContextualized(Context context) { + return new PgpGeneralException(context.getString(mMessageId), this); + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 404c128a1..2b40300d7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -19,46 +19,47 @@ package org.sufficientlysecure.keychain.provider; import android.net.Uri; import android.provider.BaseColumns; + import org.sufficientlysecure.keychain.Constants; public class KeychainContract { interface KeyRingsColumns { String MASTER_KEY_ID = "master_key_id"; // not a database id - String TYPE = "type"; // see KeyTypes String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob } interface KeysColumns { + String MASTER_KEY_ID = "master_key_id"; // not a database id + String RANK = "rank"; + String KEY_ID = "key_id"; // not a database id - String TYPE = "type"; // see KeyTypes - String IS_MASTER_KEY = "is_master_key"; String ALGORITHM = "algorithm"; + String FINGERPRINT = "fingerprint"; + String KEY_SIZE = "key_size"; - String CAN_CERTIFY = "can_certify"; String CAN_SIGN = "can_sign"; String CAN_ENCRYPT = "can_encrypt"; + String CAN_CERTIFY = "can_certify"; String IS_REVOKED = "is_revoked"; + String CREATION = "creation"; String EXPIRY = "expiry"; - String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID - String KEY_DATA = "key_data"; // PGPPublicKey/PGPSecretKey blob - String RANK = "rank"; - String FINGERPRINT = "fingerprint"; } interface UserIdsColumns { - String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID + String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID String USER_ID = "user_id"; // not a database id - String RANK = "rank"; + String RANK = "rank"; // ONLY used for sorting! no key, no nothing! + String IS_PRIMARY = "is_primary"; } interface CertsColumns { - String KEY_RING_ROW_ID = "key_ring_row_id"; // verified id, foreign key to key_rings._ID + String MASTER_KEY_ID = "master_key_id"; // verified id, foreign key to key_rings._ID String RANK = "rank"; // rank of verified key - String KEY_ID = "key_id"; // verified id, not a database id String KEY_ID_CERTIFIER = "key_id_certifier"; // verifying id, not a database id String CREATION = "creation"; + String EXPIRY = "expiry"; String VERIFIED = "verified"; String KEY_DATA = "key_data"; // certification blob } @@ -66,10 +67,15 @@ public class KeychainContract { interface ApiAppsColumns { String PACKAGE_NAME = "package_name"; String PACKAGE_SIGNATURE = "package_signature"; + } + + interface ApiAppsAccountsColumns { + String ACCOUNT_NAME = "account_name"; String KEY_ID = "key_id"; // not a database id String ENCRYPTION_ALGORITHM = "encryption_algorithm"; String HASH_ALORITHM = "hash_algorithm"; String COMPRESSION = "compression"; + String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name } public static final class KeyTypes { @@ -85,97 +91,81 @@ public class KeychainContract { public static final String BASE_KEY_RINGS = "key_rings"; public static final String BASE_DATA = "data"; + 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_PUBLIC = "public"; public static final String PATH_SECRET = "secret"; - - public static final String PATH_BY_MASTER_KEY_ID = "master_key_id"; - public static final String PATH_BY_KEY_ID = "key_id"; - public static final String PATH_BY_KEY_ROW_ID = "key_row_id"; - public static final String PATH_BY_CERTIFIER_ID = "certifier_id"; - public static final String PATH_BY_EMAILS = "emails"; - public static final String PATH_BY_LIKE_EMAIL = "like_email"; - public static final String PATH_USER_IDS = "user_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_BY_PACKAGE_NAME = "package_name"; + public static final String PATH_ACCOUNTS = "accounts"; - public static final String BASE_CERTS = "certs"; + public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns { + public static final String MASTER_KEY_ID = "master_key_id"; + public static final String HAS_SECRET = "has_secret"; - public static class KeyRings implements KeyRingsColumns, 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.thialfihar.apg.key_ring"; - - /** - * Use if a single item is returned - */ - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring"; + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring"; public static Uri buildUnifiedKeyRingsUri() { - return CONTENT_URI; + return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build(); } - public static Uri buildPublicKeyRingsUri() { + public static Uri buildGenericKeyRingUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(masterKeyId).build(); + } + public static Uri buildUnifiedKeyRingUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(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 buildUnifiedKeyRingsFindBySubkeyUri(String subkey) { + return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_SUBKEY).appendPath(subkey).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.sufficientlysecure.openkeychain.key_ring_data"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring_data"; + + public static Uri buildPublicKeyRingUri() { return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build(); } - - public static Uri buildPublicKeyRingsUri(String keyRingRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId).build(); + public static Uri buildPublicKeyRingUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(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 buildPublicKeyRingsByMasterKeyIdUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC) - .appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build(); - } - - public static Uri buildPublicKeyRingsByKeyIdUri(String keyId) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_KEY_ID) - .appendPath(keyId).build(); - } - - public static Uri buildPublicKeyRingsByEmailsUri(String emails) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_EMAILS) - .appendPath(emails).build(); - } - - public static Uri buildPublicKeyRingsByLikeEmailUri(String emails) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_LIKE_EMAIL) - .appendPath(emails).build(); - } - - public static Uri buildSecretKeyRingsUri() { + public static Uri buildSecretKeyRingUri() { return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build(); } - - public static Uri buildSecretKeyRingsUri(String keyRingRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId).build(); + public static Uri buildSecretKeyRingUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(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 Uri buildSecretKeyRingsByMasterKeyIdUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET) - .appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build(); - } - - public static Uri buildSecretKeyRingsByKeyIdUri(String keyId) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_KEY_ID) - .appendPath(keyId).build(); - } - - public static Uri buildSecretKeyRingsByEmailsUri(String emails) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_EMAILS) - .appendPath(emails).build(); - } - - public static Uri buildSecretKeyRingsByLikeEmails(String emails) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_LIKE_EMAIL) - .appendPath(emails).build(); - } } public static class Keys implements KeysColumns, BaseColumns { @@ -185,82 +175,42 @@ public class KeychainContract { /** * Use if multiple items get returned */ - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key"; + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key"; /** * Use if a single item is returned */ - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key"; - public static Uri buildPublicKeysUri(String keyRingRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId) - .appendPath(PATH_KEYS).build(); + public static Uri buildKeysUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(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 Uri buildPublicKeysUri(String keyRingRowId, String keyRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId) - .appendPath(PATH_KEYS).appendPath(keyRowId).build(); - } - - public static Uri buildSecretKeysUri(String keyRingRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId) - .appendPath(PATH_KEYS).build(); - } - - public static Uri buildSecretKeysUri(String keyRingRowId, String keyRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId) - .appendPath(PATH_KEYS).appendPath(keyRowId).build(); - } - - public static Uri buildKeysUri(Uri keyRingUri) { - return keyRingUri.buildUpon().appendPath(PATH_KEYS).build(); - } - - public static Uri buildKeysUri(Uri keyRingUri, String keyRowId) { - return keyRingUri.buildUpon().appendPath(PATH_KEYS).appendPath(keyRowId).build(); - } } public static class UserIds implements UserIdsColumns, BaseColumns { + public static final String VERIFIED = "verified"; 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.thialfihar.apg.user_id"; + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.user_id"; /** * Use if a single item is returned */ - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.user_id"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.user_id"; - public static Uri buildPublicUserIdsUri(String keyRingRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId) - .appendPath(PATH_USER_IDS).build(); + public static Uri buildUserIdsUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_USER_IDS).build(); } - - public static Uri buildPublicUserIdsUri(String keyRingRowId, String userIdRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId) - .appendPath(PATH_USER_IDS).appendPath(userIdRowId).build(); - } - - public static Uri buildSecretUserIdsUri(String keyRingRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId) - .appendPath(PATH_USER_IDS).build(); - } - - public static Uri buildSecretUserIdsUri(String keyRingRowId, String userIdRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId) - .appendPath(PATH_USER_IDS).appendPath(userIdRowId).build(); - } - - public static Uri buildUserIdsUri(Uri keyRingUri) { - return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).build(); - } - - public static Uri buildUserIdsUri(Uri keyRingUri, String userIdRowId) { - return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).appendPath(userIdRowId).build(); + public static Uri buildUserIdsUri(Uri uri) { + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build(); } } @@ -271,45 +221,55 @@ public class KeychainContract { /** * Use if multiple items get returned */ - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps"; + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.api_apps"; /** * Use if a single item is returned */ - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps"; - - public static Uri buildIdUri(String rowId) { - return CONTENT_URI.buildUpon().appendPath(rowId).build(); - } + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.api_app"; public static Uri buildByPackageNameUri(String packageName) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName) + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build(); + } + } + + public static class ApiAccounts implements ApiAppsAccountsColumns, 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.sufficientlysecure.openkeychain.api_app.accounts"; + + /** + * Use if a single item is returned + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.api_app.account"; + + public static Uri buildBaseUri(String packageName) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS) .build(); } + + public static Uri buildByPackageAndAccountUri(String packageName, String accountName) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS) + .appendEncodedPath(accountName).build(); + } } public static class Certs implements CertsColumns, BaseColumns { + public static final String USER_ID = UserIdsColumns.USER_ID; + public static final String SIGNER_UID = "signer_user_id"; + public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_CERTS).build(); + .appendPath(BASE_KEY_RINGS).build(); - // do we even need this one...? just using it as default for database insert notifications~ - public static Uri buildCertsUri(String rowId) { - return CONTENT_URI.buildUpon().appendPath(rowId).build(); + public static Uri buildCertsUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build(); } - - public static Uri buildCertsByKeyRowIdUri(String keyRingRowId) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ROW_ID) - .appendPath(keyRingRowId).build(); - } - - public static Uri buildCertsByKeyIdUri(String keyId) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(keyId) - .build(); - } - - public static Uri buildCertsByCertifierKeyIdUri(String keyId) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_CERTIFIER_ID).appendPath(keyId) - .build(); + public static Uri buildCertsUri(Uri uri) { + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build(); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index cc6a1f1e1..fda1783cb 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -18,97 +18,156 @@ package org.sufficientlysecure.keychain.provider; import android.content.Context; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; + +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKeyRing; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.util.Log; +import java.io.IOException; + public class KeychainDatabase extends SQLiteOpenHelper { - private static final String DATABASE_NAME = "apg.db"; - private static final int DATABASE_VERSION = 8; + private static final String DATABASE_NAME = "openkeychain.db"; + private static final int DATABASE_VERSION = 1; + static Boolean apg_hack = false; public interface Tables { - String KEY_RINGS = "key_rings"; + String KEY_RINGS_PUBLIC = "keyrings_public"; + String KEY_RINGS_SECRET = "keyrings_secret"; String KEYS = "keys"; String USER_IDS = "user_ids"; - String API_APPS = "api_apps"; String CERTS = "certs"; + String API_APPS = "api_apps"; + String API_ACCOUNTS = "api_accounts"; } - private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS - + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + KeyRingsColumns.MASTER_KEY_ID + " INT64, " - + KeyRingsColumns.TYPE + " INTEGER, " - + KeyRingsColumns.KEY_RING_DATA + " BLOB)"; + 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 static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + KeysColumns.KEY_ID + " INT64, " - + KeysColumns.TYPE + " INTEGER, " - + KeysColumns.IS_MASTER_KEY + " INTEGER, " - + KeysColumns.ALGORITHM + " INTEGER, " - + KeysColumns.KEY_SIZE + " INTEGER, " - + KeysColumns.CAN_CERTIFY + " INTEGER, " - + KeysColumns.CAN_SIGN + " INTEGER, " - + KeysColumns.CAN_ENCRYPT + " INTEGER, " - + KeysColumns.IS_REVOKED + " INTEGER, " - + KeysColumns.CREATION + " INTEGER, " - + KeysColumns.EXPIRY + " INTEGER, " - + KeysColumns.KEY_DATA + " BLOB," - + KeysColumns.RANK + " INTEGER, " - + KeysColumns.FINGERPRINT + " BLOB, " - + KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY(" - + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "(" - + BaseColumns._ID + ") ON DELETE CASCADE)"; + 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" + + ")"; - private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS - + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + UserIdsColumns.USER_ID + " TEXT, " - + UserIdsColumns.RANK + " INTEGER, " - + UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY(" - + UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "(" - + BaseColumns._ID + ") ON DELETE CASCADE)"; + private static final String CREATE_KEYS = + "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " (" + + KeysColumns.MASTER_KEY_ID + " INTEGER, " + + KeysColumns.RANK + " INTEGER, " + + + KeysColumns.KEY_ID + " INTEGER, " + + KeysColumns.KEY_SIZE + " INTEGER, " + + KeysColumns.ALGORITHM + " INTEGER, " + + KeysColumns.FINGERPRINT + " BLOB, " + + + KeysColumns.CAN_CERTIFY + " BOOLEAN, " + + KeysColumns.CAN_SIGN + " BOOLEAN, " + + KeysColumns.CAN_ENCRYPT + " BOOLEAN, " + + KeysColumns.IS_REVOKED + " BOOLEAN, " + + + 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_IDS = + "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS + "(" + + UserIdsColumns.MASTER_KEY_ID + " INTEGER, " + + UserIdsColumns.USER_ID + " CHARMANDER, " + + + UserIdsColumns.IS_PRIMARY + " BOOLEAN, " + + UserIdsColumns.RANK+ " INTEGER, " + + + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), " + + "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), " + + "FOREIGN KEY(" + UserIdsColumns.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.CREATION + " INTEGER, " + + CertsColumns.EXPIRY + " INTEGER, " + + CertsColumns.VERIFIED + " INTEGER, " + + + CertsColumns.KEY_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_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") 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 UNIQUE, " - + ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB, " - + ApiAppsColumns.KEY_ID + " INT64, " - + ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, " - + ApiAppsColumns.HASH_ALORITHM + " INTEGER, " - + ApiAppsColumns.COMPRESSION + " INTEGER)"; + + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, " + + ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB)"; - private static final String CREATE_CERTS = "CREATE TABLE IF NOT EXISTS " + Tables.CERTS + private static final String CREATE_API_APPS_ACCOUNTS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ACCOUNTS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + CertsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL " - + " REFERENCES " + Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE, " - + CertsColumns.KEY_ID + " INTEGER, " // certified key - + CertsColumns.RANK + " INTEGER, " // key rank of certified uid - + CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key - + CertsColumns.CREATION + " INTEGER, " - + CertsColumns.VERIFIED + " INTEGER, " - + CertsColumns.KEY_DATA + " BLOB)"; - + + ApiAppsAccountsColumns.ACCOUNT_NAME + " TEXT NOT NULL, " + + ApiAppsAccountsColumns.KEY_ID + " INT64, " + + ApiAppsAccountsColumns.ENCRYPTION_ALGORITHM + " INTEGER, " + + ApiAppsAccountsColumns.HASH_ALORITHM + " INTEGER, " + + ApiAppsAccountsColumns.COMPRESSION + " INTEGER, " + + ApiAppsAccountsColumns.PACKAGE_NAME + " TEXT NOT NULL, " + + "UNIQUE(" + ApiAppsAccountsColumns.ACCOUNT_NAME + ", " + + ApiAppsAccountsColumns.PACKAGE_NAME + "), " + + "FOREIGN KEY(" + ApiAppsAccountsColumns.PACKAGE_NAME + ") REFERENCES " + + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE)"; KeychainDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); + + // make sure this is only done once, on the first instance! + boolean iAmIt = false; + synchronized(apg_hack) { + if(!apg_hack) { + iAmIt = true; + apg_hack = true; + } + } + // if it's us, do the import + if(iAmIt) + checkAndImportApg(context); } @Override public void onCreate(SQLiteDatabase db) { Log.w(Constants.TAG, "Creating database..."); - db.execSQL(CREATE_KEY_RINGS); + db.execSQL(CREATE_KEYRINGS_PUBLIC); + db.execSQL(CREATE_KEYRINGS_SECRET); db.execSQL(CREATE_KEYS); db.execSQL(CREATE_USER_IDS); - db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_CERTS); + db.execSQL(CREATE_API_APPS); + db.execSQL(CREATE_API_APPS_ACCOUNTS); } @Override @@ -117,47 +176,100 @@ public class KeychainDatabase extends SQLiteOpenHelper { if (!db.isReadOnly()) { // Enable foreign key constraints db.execSQL("PRAGMA foreign_keys=ON;"); + // TODO remove, once we remove the "always migrate" debug stuff + // db.execSQL("DROP TABLE certs;"); + // db.execSQL("DROP TABLE user_ids;"); + db.execSQL(CREATE_USER_IDS); + db.execSQL(CREATE_CERTS); } } @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); + public void onUpgrade(SQLiteDatabase db, int old, int nu) { + // don't care (this is version 1) + } - // Upgrade from oldVersion through all cases to newest one - for (int version = oldVersion; version < newVersion; ++version) { - Log.w(Constants.TAG, "Upgrading database to version " + version); + /** This method tries to import data from a provided database. + * + * The sole assumptions made on this db are that there is a key_rings table + * with a key_ring_data and a type column, the latter of which should be bigger + * for secret keys. + */ + public void checkAndImportApg(Context context) { - switch (version) { - case 3: - db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY - + " INTEGER DEFAULT 0;"); - db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY - + " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;"); + boolean hasApgDb = false; { + // It's the Java way =( + String[] dbs = context.databaseList(); + for(String db : dbs) { + if(db.equals("apg.db")) { + hasApgDb = true; break; - case 4: - db.execSQL(CREATE_API_APPS); - break; - case 5: - // new column: package_signature - db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS); - db.execSQL(CREATE_API_APPS); - break; - case 6: - // new column: fingerprint - db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT - + " BLOB;"); - break; - case 7: - // new table: certs - db.execSQL(CREATE_CERTS); - - break; - default: - break; - + } } } + + if(!hasApgDb) + return; + + Log.d(Constants.TAG, "apg.db exists! Importing..."); + + SQLiteDatabase db = new SQLiteOpenHelper(context, "apg.db", null, 1) { + @Override + public void onCreate(SQLiteDatabase db) { + // should never happen + assert false; + } + @Override + public void onDowngrade(SQLiteDatabase db, int old, int nu) { + // don't care + } + @Override + public void onUpgrade(SQLiteDatabase db, int old, int nu) { + // don't care either + } + }.getReadableDatabase(); + + // kill current! + { // TODO don't kill current. + Log.d(Constants.TAG, "Truncating db..."); + SQLiteDatabase d = getWritableDatabase(); + d.execSQL("DELETE FROM keyrings_public"); + d.close(); + Log.d(Constants.TAG, "Ok."); + } + + Cursor c = db.rawQuery("SELECT key_ring_data FROM key_rings ORDER BY type ASC", null); + try { + // import from old database + Log.d(Constants.TAG, "Importing " + c.getCount() + " keyrings from apg.db..."); + for(int i = 0; i < c.getCount(); i++) { + c.moveToPosition(i); + byte[] data = c.getBlob(0); + PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); + if(ring instanceof PGPPublicKeyRing) + ProviderHelper.saveKeyRing(context, (PGPPublicKeyRing) ring); + else if(ring instanceof PGPSecretKeyRing) + ProviderHelper.saveKeyRing(context, (PGPSecretKeyRing) ring); + else { + Log.e(Constants.TAG, "Unknown blob data type!"); + } + } + + } catch(IOException e) { + Log.e(Constants.TAG, "Error importing apg db!", e); + return; + } finally { + if(c != null) + c.close(); + if(db != null) + db.close(); + } + + // TODO delete old db, if we are sure this works + // context.deleteDatabase("apg.db"); + Log.d(Constants.TAG, "All done, (not) deleting apg.db"); + + } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index f34423a71..83b3dd744 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Dominik Schürmann + * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2010 Thialfihar * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,10 +26,16 @@ import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; -import android.provider.BaseColumns; import android.text.TextUtils; + import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.KeychainContract.*; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.util.Log; @@ -37,54 +43,29 @@ import java.util.Arrays; import java.util.HashMap; public class KeychainProvider extends ContentProvider { - // public static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME - // + ".action.DATABASE_CHANGE"; - // - // public static final String EXTRA_BROADCAST_KEY_TYPE = "key_type"; - // public static final String EXTRA_BROADCAST_CONTENT_ITEM_TYPE = "contentItemType"; - private static final int PUBLIC_KEY_RING = 101; - private static final int PUBLIC_KEY_RING_BY_ROW_ID = 102; - private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID = 103; - private static final int PUBLIC_KEY_RING_BY_KEY_ID = 104; - private static final int PUBLIC_KEY_RING_BY_EMAILS = 105; - private static final int PUBLIC_KEY_RING_BY_LIKE_EMAIL = 106; + private static final int KEY_RINGS_UNIFIED = 101; + private static final int KEY_RINGS_PUBLIC = 102; + private static final int KEY_RINGS_SECRET = 103; - private static final int PUBLIC_KEY_RING_KEY = 111; - private static final int PUBLIC_KEY_RING_KEY_BY_ROW_ID = 112; - - private static final int PUBLIC_KEY_RING_USER_ID = 121; - private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122; - private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID = 123; - - private static final int SECRET_KEY_RING = 201; - private static final int SECRET_KEY_RING_BY_ROW_ID = 202; - private static final int SECRET_KEY_RING_BY_MASTER_KEY_ID = 203; - private static final int SECRET_KEY_RING_BY_KEY_ID = 204; - private static final int SECRET_KEY_RING_BY_EMAILS = 205; - private static final int SECRET_KEY_RING_BY_LIKE_EMAIL = 206; - - private static final int SECRET_KEY_RING_KEY = 211; - private static final int SECRET_KEY_RING_KEY_BY_ROW_ID = 212; - - private static final int SECRET_KEY_RING_USER_ID = 221; - private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222; + private static final int KEY_RING_UNIFIED = 200; + private static final int KEY_RING_KEYS = 201; + private static final int KEY_RING_USER_IDS = 202; + private static final int KEY_RING_PUBLIC = 203; + private static final int KEY_RING_SECRET = 204; + private static final int KEY_RING_CERTS = 205; private static final int API_APPS = 301; - private static final int API_APPS_BY_ROW_ID = 302; private static final int API_APPS_BY_PACKAGE_NAME = 303; + private static final int API_ACCOUNTS = 304; + private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306; - private static final int UNIFIED_KEY_RING = 401; + private static final int KEY_RINGS_FIND_BY_EMAIL = 400; + private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; - private static final int CERTS = 401; - private static final int CERTS_BY_KEY_ID = 402; - private static final int CERTS_BY_ROW_ID = 403; - private static final int CERTS_BY_KEY_ROW_ID = 404; - private static final int CERTS_BY_KEY_ROW_ID_ALL = 405; - private static final int CERTS_BY_CERTIFIER_ID = 406; - private static final int CERTS_BY_KEY_ROW_ID_HAS_SECRET = 407; + private static final int CERTS_FIND_BY_CERTIFIER_ID = 501; - // private static final int DATA_STREAM = 401; + // private static final int DATA_STREAM = 501; protected UriMatcher mUriMatcher; @@ -98,171 +79,85 @@ public class KeychainProvider extends ContentProvider { String authority = KeychainContract.CONTENT_AUTHORITY; /** - * unified key rings - * - *

-         * key_rings
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS, UNIFIED_KEY_RING); - - /** - * public key rings + * list key_rings * *
+         * key_rings/unified
          * key_rings/public
-         * key_rings/public/#
-         * key_rings/public/master_key_id/_
-         * key_rings/public/key_id/_
-         * key_rings/public/emails/_
-         * key_rings/public/like_email/_
          * 
*/ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC, PUBLIC_KEY_RING); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/#", PUBLIC_KEY_RING_BY_ROW_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID - + "/*", PUBLIC_KEY_RING_BY_MASTER_KEY_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_KEY_ID + "/*", - PUBLIC_KEY_RING_BY_KEY_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS + "/*", - PUBLIC_KEY_RING_BY_EMAILS); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS, - PUBLIC_KEY_RING_BY_EMAILS); // without emails specified - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*", - PUBLIC_KEY_RING_BY_LIKE_EMAIL); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + + "/" + KeychainContract.PATH_UNIFIED, + KEY_RINGS_UNIFIED); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + + "/" + KeychainContract.PATH_PUBLIC, + KEY_RINGS_PUBLIC); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + + "/" + KeychainContract.PATH_SECRET, + KEY_RINGS_SECRET); /** - * public keys + * find by criteria other than master key id + * + * key_rings/find/email/_ + * key_rings/find/subkey/_ * - *
-         * key_rings/public/#/keys
-         * key_rings/public/#/keys/#
-         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS, - PUBLIC_KEY_RING_KEY); + + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_EMAIL + "/*", + KEY_RINGS_FIND_BY_EMAIL); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS + "/#", - PUBLIC_KEY_RING_KEY_BY_ROW_ID); + + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_SUBKEY + "/*", + KEY_RINGS_FIND_BY_SUBKEY); /** - * public user ids + * list key_ring specifics * *
-         * key_rings/public/#/user_ids
-         * key_rings/public/#/user_ids/#
-         * key_rings/public/master_key_id/#/user_ids
+         * key_rings/_/unified
+         * key_rings/_/keys
+         * key_rings/_/user_ids
+         * key_rings/_/public
+         * key_rings/_/secret
          * 
*/ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS, - PUBLIC_KEY_RING_USER_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#", - PUBLIC_KEY_RING_USER_ID_BY_ROW_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_PUBLIC + "/" - + KeychainContract.PATH_BY_MASTER_KEY_ID + "/*/" + KeychainContract.PATH_USER_IDS, - PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID); - - /** - * secret key rings - * - *
-         * key_rings/secret
-         * key_rings/secret/#
-         * key_rings/secret/master_key_id/_
-         * key_rings/secret/key_id/_
-         * key_rings/secret/emails/_
-         * key_rings/secret/like_email/_
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET, SECRET_KEY_RING); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/#", SECRET_KEY_RING_BY_ROW_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID - + "/*", SECRET_KEY_RING_BY_MASTER_KEY_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_KEY_ID + "/*", - SECRET_KEY_RING_BY_KEY_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS + "/*", - SECRET_KEY_RING_BY_EMAILS); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS, - SECRET_KEY_RING_BY_EMAILS); // without emails specified - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*", - SECRET_KEY_RING_BY_LIKE_EMAIL); - - /** - * secret keys - * - *
-         * key_rings/secret/#/keys
-         * key_rings/secret/#/keys/#
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS, - SECRET_KEY_RING_KEY); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS + "/#", - SECRET_KEY_RING_KEY_BY_ROW_ID); - - /** - * secret user ids - * - *
-         * key_rings/secret/#/user_ids
-         * key_rings/secret/#/user_ids/#
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS, - SECRET_KEY_RING_USER_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS + "/#", - SECRET_KEY_RING_USER_ID_BY_ROW_ID); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_UNIFIED, + KEY_RING_UNIFIED); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_KEYS, + KEY_RING_KEYS); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_USER_IDS, + KEY_RING_USER_IDS); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_PUBLIC, + KEY_RING_PUBLIC); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_SECRET, + KEY_RING_SECRET); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_CERTS, + KEY_RING_CERTS); /** * API apps - */ - matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS); - matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID); - matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/" - + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME); - - /** - /** - * certifications * *
-         * 
+ * api_apps + * api_apps/_ (package name) * + * api_apps/_/accounts + * api_apps/_/accounts/_ (account name) + * */ - matcher.addURI(authority, KeychainContract.BASE_CERTS, CERTS); - matcher.addURI(authority, KeychainContract.BASE_CERTS + "/#", CERTS_BY_ROW_ID); - matcher.addURI(authority, KeychainContract.BASE_CERTS + "/" - + KeychainContract.PATH_BY_KEY_ROW_ID + "/#", CERTS_BY_KEY_ROW_ID); - matcher.addURI(authority, KeychainContract.BASE_CERTS + "/" - + KeychainContract.PATH_BY_KEY_ROW_ID + "/#/all", CERTS_BY_KEY_ROW_ID_ALL); - matcher.addURI(authority, KeychainContract.BASE_CERTS + "/" - + KeychainContract.PATH_BY_KEY_ROW_ID + "/#/has_secret", CERTS_BY_KEY_ROW_ID_HAS_SECRET); - matcher.addURI(authority, KeychainContract.BASE_CERTS + "/" - + KeychainContract.PATH_BY_KEY_ID + "/#", CERTS_BY_KEY_ID); - matcher.addURI(authority, KeychainContract.BASE_CERTS + "/" - + KeychainContract.PATH_BY_CERTIFIER_ID + "/#", CERTS_BY_CERTIFIER_ID); + matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS); + matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); + + matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" + + KeychainContract.PATH_ACCOUNTS, API_ACCOUNTS); + matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" + + KeychainContract.PATH_ACCOUNTS + "/*", API_ACCOUNTS_BY_ACCOUNT_NAME); /** * data stream @@ -276,7 +171,7 @@ public class KeychainProvider extends ContentProvider { return matcher; } - private KeychainDatabase mApgDatabase; + private KeychainDatabase mKeychainDatabase; /** * {@inheritDoc} @@ -284,10 +179,15 @@ public class KeychainProvider extends ContentProvider { @Override public boolean onCreate() { mUriMatcher = buildUriMatcher(); - mApgDatabase = new KeychainDatabase(getContext()); return true; } + public KeychainDatabase getDb() { + if(mKeychainDatabase == null) + mKeychainDatabase = new KeychainDatabase(getContext()); + return mKeychainDatabase; + } + /** * {@inheritDoc} */ @@ -295,247 +195,44 @@ public class KeychainProvider extends ContentProvider { public String getType(Uri uri) { final int match = mUriMatcher.match(uri); switch (match) { - case PUBLIC_KEY_RING: - case PUBLIC_KEY_RING_BY_EMAILS: - case PUBLIC_KEY_RING_BY_LIKE_EMAIL: - case SECRET_KEY_RING: - case SECRET_KEY_RING_BY_EMAILS: - case SECRET_KEY_RING_BY_LIKE_EMAIL: - return KeyRings.CONTENT_TYPE; - - case PUBLIC_KEY_RING_BY_ROW_ID: - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: - case PUBLIC_KEY_RING_BY_KEY_ID: - case SECRET_KEY_RING_BY_ROW_ID: - case SECRET_KEY_RING_BY_MASTER_KEY_ID: - case SECRET_KEY_RING_BY_KEY_ID: + case KEY_RING_PUBLIC: return KeyRings.CONTENT_ITEM_TYPE; - case PUBLIC_KEY_RING_KEY: - case SECRET_KEY_RING_KEY: + case KEY_RING_KEYS: return Keys.CONTENT_TYPE; - case PUBLIC_KEY_RING_KEY_BY_ROW_ID: - case SECRET_KEY_RING_KEY_BY_ROW_ID: - return Keys.CONTENT_ITEM_TYPE; - - case PUBLIC_KEY_RING_USER_ID: - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID: - case SECRET_KEY_RING_USER_ID: + case KEY_RING_USER_IDS: return UserIds.CONTENT_TYPE; - case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: - case SECRET_KEY_RING_USER_ID_BY_ROW_ID: - return UserIds.CONTENT_ITEM_TYPE; + case KEY_RING_SECRET: + return KeyRings.CONTENT_ITEM_TYPE; case API_APPS: return ApiApps.CONTENT_TYPE; - case API_APPS_BY_ROW_ID: case API_APPS_BY_PACKAGE_NAME: return ApiApps.CONTENT_ITEM_TYPE; + case API_ACCOUNTS: + return ApiAccounts.CONTENT_TYPE; + + case API_ACCOUNTS_BY_ACCOUNT_NAME: + return ApiAccounts.CONTENT_ITEM_TYPE; + default: throw new UnsupportedOperationException("Unknown uri: " + uri); } } - /** - * Returns type of the query (secret/public) - * - * @param match - * @return - */ - private int getKeyType(int match) { - int type; - switch (match) { - case PUBLIC_KEY_RING: - case PUBLIC_KEY_RING_BY_ROW_ID: - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: - case PUBLIC_KEY_RING_BY_KEY_ID: - case PUBLIC_KEY_RING_BY_EMAILS: - case PUBLIC_KEY_RING_BY_LIKE_EMAIL: - case PUBLIC_KEY_RING_KEY: - case PUBLIC_KEY_RING_KEY_BY_ROW_ID: - case PUBLIC_KEY_RING_USER_ID: - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID: - case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: - type = KeyTypes.PUBLIC; - break; - - case SECRET_KEY_RING: - case SECRET_KEY_RING_BY_ROW_ID: - case SECRET_KEY_RING_BY_MASTER_KEY_ID: - case SECRET_KEY_RING_BY_KEY_ID: - case SECRET_KEY_RING_BY_EMAILS: - case SECRET_KEY_RING_BY_LIKE_EMAIL: - case SECRET_KEY_RING_KEY: - case SECRET_KEY_RING_KEY_BY_ROW_ID: - case SECRET_KEY_RING_USER_ID: - case SECRET_KEY_RING_USER_ID_BY_ROW_ID: - type = KeyTypes.SECRET; - break; - - default: - Log.e(Constants.TAG, "Unknown match " + match); - type = -1; - break; - } - - return type; - } - - /** - * Set result of query to specific columns, don't show blob column - * - * @return - */ - private HashMap getProjectionMapForKeyRings() { - HashMap projectionMap = new HashMap(); - - projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID); - projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "." - + KeyRingsColumns.KEY_RING_DATA); - projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." - + KeyRingsColumns.MASTER_KEY_ID); - // TODO: deprecated master key id - //projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID); - - projectionMap.put(KeysColumns.ALGORITHM, Tables.KEYS + "." + KeysColumns.ALGORITHM); - projectionMap.put(KeysColumns.KEY_SIZE, Tables.KEYS + "." + KeysColumns.KEY_SIZE); - projectionMap.put(KeysColumns.CREATION, Tables.KEYS + "." + KeysColumns.CREATION); - projectionMap.put(KeysColumns.EXPIRY, Tables.KEYS + "." + KeysColumns.EXPIRY); - projectionMap.put(KeysColumns.KEY_RING_ROW_ID, Tables.KEYS + "." + KeysColumns.KEY_RING_ROW_ID); - projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT); - projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED); - - projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); - - // type attribute is special: if there is any grouping, choose secret over public type - projectionMap.put(KeyRingsColumns.TYPE, - "MAX(" + Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + ") AS " + KeyRingsColumns.TYPE); - - return projectionMap; - } - - /** - * Set result of query to specific columns, don't show blob column - * - * @return - */ - private HashMap getProjectionMapForKeys() { - HashMap projectionMap = new HashMap(); - - projectionMap.put(BaseColumns._ID, BaseColumns._ID); - projectionMap.put(KeysColumns.KEY_ID, KeysColumns.KEY_ID); - projectionMap.put(KeysColumns.IS_MASTER_KEY, KeysColumns.IS_MASTER_KEY); - projectionMap.put(KeysColumns.ALGORITHM, KeysColumns.ALGORITHM); - projectionMap.put(KeysColumns.KEY_SIZE, KeysColumns.KEY_SIZE); - projectionMap.put(KeysColumns.CAN_CERTIFY, KeysColumns.CAN_CERTIFY); - projectionMap.put(KeysColumns.CAN_SIGN, KeysColumns.CAN_SIGN); - projectionMap.put(KeysColumns.CAN_ENCRYPT, KeysColumns.CAN_ENCRYPT); - projectionMap.put(KeysColumns.IS_REVOKED, KeysColumns.IS_REVOKED); - projectionMap.put(KeysColumns.CREATION, KeysColumns.CREATION); - projectionMap.put(KeysColumns.EXPIRY, KeysColumns.EXPIRY); - projectionMap.put(KeysColumns.KEY_RING_ROW_ID, KeysColumns.KEY_RING_ROW_ID); - projectionMap.put(KeysColumns.KEY_DATA, KeysColumns.KEY_DATA); - projectionMap.put(KeysColumns.RANK, KeysColumns.RANK); - projectionMap.put(KeysColumns.FINGERPRINT, KeysColumns.FINGERPRINT); - - return projectionMap; - } - - private HashMap getProjectionMapForUserIds() { - HashMap projectionMap = new HashMap(); - - projectionMap.put(BaseColumns._ID, Tables.USER_IDS + "." + BaseColumns._ID); - projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); - projectionMap.put(UserIdsColumns.RANK, Tables.USER_IDS + "." + UserIdsColumns.RANK); - projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." - + KeyRingsColumns.MASTER_KEY_ID); - - // this is the count of known secret keys who certified this uid - projectionMap.put("verified", "COUNT(" + Tables.KEYS + "." + Keys._ID + ") AS verified"); - - return projectionMap; - } - private HashMap getProjectionMapForCerts() { - - HashMap pmap = new HashMap(); - pmap.put(Certs._ID, Tables.CERTS + "." + Certs._ID); - pmap.put(Certs.KEY_ID, Tables.CERTS + "." + Certs.KEY_ID); - pmap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK); - pmap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); - pmap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); - pmap.put(Certs.KEY_DATA, Tables.CERTS + "." + Certs.KEY_DATA); - pmap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); - // verified key data - pmap.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); - // verifying key data - pmap.put("signer_uid", "signer." + UserIds.USER_ID + " AS signer_uid"); - - return pmap; - } - - /** - * Builds default query for keyRings: KeyRings table is joined with UserIds and Keys - */ - private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match) { - if (match != UNIFIED_KEY_RING) { - // public or secret keyring - qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = "); - qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); - } - - // join keyrings with keys and userIds - // Only get user id and key with rank 0 (main user id and main key) - qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "(" - + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "." - + KeysColumns.KEY_RING_ROW_ID + " AND " + Tables.KEYS + "." - + KeysColumns.RANK + " = '0') " + " INNER JOIN " + Tables.USER_IDS + " ON " - + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "." - + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "." - + UserIdsColumns.RANK + " = '0')"); - - qb.setProjectionMap(getProjectionMapForKeyRings()); - - return qb; - } - - /** - * Builds default query for keyRings: KeyRings table is joined with UserIds and Keys - *

- * Here only one key should be selected in the query to return a single keyring! - */ - private SQLiteQueryBuilder buildKeyRingQueryWithSpecificKey(SQLiteQueryBuilder qb, int match) { - // public or secret keyring - qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = "); - qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); - - // join keyrings with keys and userIds to every keyring - qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "(" - + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "." - + KeysColumns.KEY_RING_ROW_ID + ") " + " INNER JOIN " + Tables.USER_IDS + " ON " - + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "." - + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "." - + UserIdsColumns.RANK + " = '0')"); - - qb.setProjectionMap(getProjectionMapForKeyRings()); - - return qb; - } - /** * {@inheritDoc} */ - @SuppressWarnings("deprecation") @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - SQLiteDatabase db = mApgDatabase.getReadableDatabase(); int match = mUriMatcher.match(uri); @@ -543,250 +240,254 @@ public class KeychainProvider extends ContentProvider { String groupBy = null, having = null; switch (match) { - case UNIFIED_KEY_RING: - qb = buildKeyRingQuery(qb, match); + case KEY_RING_UNIFIED: + case KEY_RINGS_UNIFIED: + case KEY_RINGS_FIND_BY_EMAIL: + case KEY_RINGS_FIND_BY_SUBKEY: { + HashMap projectionMap = new HashMap(); + projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); + projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); + projectionMap.put(KeyRings.KEY_ID, Keys.KEY_ID); + projectionMap.put(KeyRings.KEY_SIZE, Keys.KEY_SIZE); + projectionMap.put(KeyRings.IS_REVOKED, Keys.IS_REVOKED); + projectionMap.put(KeyRings.CAN_CERTIFY, Keys.CAN_CERTIFY); + projectionMap.put(KeyRings.CAN_ENCRYPT, Keys.CAN_ENCRYPT); + projectionMap.put(KeyRings.CAN_SIGN, Keys.CAN_SIGN); + projectionMap.put(KeyRings.CREATION, Keys.CREATION); + projectionMap.put(KeyRings.EXPIRY, Keys.EXPIRY); + projectionMap.put(KeyRings.ALGORITHM, Keys.ALGORITHM); + projectionMap.put(KeyRings.FINGERPRINT, Keys.FINGERPRINT); + projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID); + projectionMap.put(KeyRings.HAS_SECRET, "(" + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL) AS " + KeyRings.HAS_SECRET); + qb.setProjectionMap(projectionMap); - // GROUP BY so we don't get duplicates - groupBy = Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID; + qb.setTables( + Tables.KEYS + + " INNER JOIN " + Tables.USER_IDS + " ON (" + + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " = " + + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = 0" + + ") LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON (" + + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " = " + + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + + ")" + ); + qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0"); - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = KeyRings.TYPE + " DESC, " + - Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; - } - - break; - - case PUBLIC_KEY_RING: - case SECRET_KEY_RING: - qb = buildKeyRingQuery(qb, match); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; - } - - break; - - case PUBLIC_KEY_RING_BY_ROW_ID: - case SECRET_KEY_RING_BY_ROW_ID: - qb = buildKeyRingQuery(qb, match); - - qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + BaseColumns._ID + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; - } - - break; - - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: - case SECRET_KEY_RING_BY_MASTER_KEY_ID: - qb = buildKeyRingQuery(qb, match); - - qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; - } - - break; - - case SECRET_KEY_RING_BY_KEY_ID: - case PUBLIC_KEY_RING_BY_KEY_ID: - qb = buildKeyRingQueryWithSpecificKey(qb, match); - - qb.appendWhere(" AND " + Tables.KEYS + "." + KeysColumns.KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; - } - - break; - - case SECRET_KEY_RING_BY_EMAILS: - case PUBLIC_KEY_RING_BY_EMAILS: - qb = buildKeyRingQuery(qb, match); - - String emails = uri.getLastPathSegment(); - String chunks[] = emails.split(" *, *"); - boolean gotCondition = false; - String emailWhere = ""; - for (int i = 0; i < chunks.length; ++i) { - if (chunks[i].length() == 0) { - continue; + switch(match) { + case KEY_RING_UNIFIED: { + qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + break; } - if (i != 0) { - emailWhere += " OR "; + case KEY_RINGS_FIND_BY_SUBKEY: { + try { + String subkey = Long.valueOf(uri.getLastPathSegment()).toString(); + qb.appendWhere(" AND EXISTS (" + + " SELECT 1 FROM " + Tables.KEYS + " AS tmp" + + " WHERE tmp." + UserIds.MASTER_KEY_ID + + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND tmp." + Keys.KEY_ID + " = " + subkey + "" + + ")"); + } catch(NumberFormatException e) { + Log.e(Constants.TAG, "Malformed find by subkey query!", e); + qb.appendWhere(" AND 0"); + } + break; + } + case KEY_RINGS_FIND_BY_EMAIL: { + String chunks[] = uri.getLastPathSegment().split(" *, *"); + boolean gotCondition = false; + String emailWhere = ""; + // JAVA ♥ + for (int i = 0; i < chunks.length; ++i) { + if (chunks[i].length() == 0) { + continue; + } + if (i != 0) { + emailWhere += " OR "; + } + emailWhere += "tmp." + UserIds.USER_ID + " LIKE "; + // match '*', so it has to be at the *end* of the user id + emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); + gotCondition = true; + } + if(gotCondition) { + qb.appendWhere(" AND EXISTS (" + + " SELECT 1 FROM " + Tables.USER_IDS + " AS tmp" + + " WHERE tmp." + UserIds.MASTER_KEY_ID + + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND (" + emailWhere + ")" + + ")"); + } else { + // TODO better way to do this? + Log.e(Constants.TAG, "Malformed find by email query!"); + qb.appendWhere(" AND 0"); + } + break; } - emailWhere += "tmp." + UserIdsColumns.USER_ID + " LIKE "; - // match '*', so it has to be at the *end* of the user id - emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); - gotCondition = true; } - if (gotCondition) { - qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM " - + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID - + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + emailWhere - + "))"); + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NULL ASC, " + + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC"; } - break; - - case SECRET_KEY_RING_BY_LIKE_EMAIL: - case PUBLIC_KEY_RING_BY_LIKE_EMAIL: - qb = buildKeyRingQuery(qb, match); - - String likeEmail = uri.getLastPathSegment(); - - String likeEmailWhere = "tmp." + UserIdsColumns.USER_ID + " LIKE " - + DatabaseUtils.sqlEscapeString("%<%" + likeEmail + "%>"); - - qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM " - + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID - + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + likeEmailWhere - + "))"); + // uri to watch is all /key_rings/ + uri = KeyRings.CONTENT_URI; break; + } + + case KEY_RING_KEYS: { + HashMap projectionMap = new HashMap(); + projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id"); + projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); + projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK); + projectionMap.put(Keys.KEY_ID, Keys.KEY_ID); + projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE); + projectionMap.put(Keys.IS_REVOKED, Keys.IS_REVOKED); + projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY); + projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); + projectionMap.put(Keys.CAN_SIGN, Keys.CAN_SIGN); + projectionMap.put(Keys.CREATION, Keys.CREATION); + projectionMap.put(Keys.EXPIRY, Keys.EXPIRY); + projectionMap.put(Keys.ALGORITHM, Keys.ALGORITHM); + projectionMap.put(Keys.FINGERPRINT, Keys.FINGERPRINT); + qb.setProjectionMap(projectionMap); - case PUBLIC_KEY_RING_KEY: - case SECRET_KEY_RING_KEY: qb.setTables(Tables.KEYS); - qb.appendWhere(KeysColumns.TYPE + " = "); - qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); - - qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(2)); - - qb.setProjectionMap(getProjectionMapForKeys()); + qb.appendWhere(Keys.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); break; + } - case PUBLIC_KEY_RING_KEY_BY_ROW_ID: - case SECRET_KEY_RING_KEY_BY_ROW_ID: - qb.setTables(Tables.KEYS); - qb.appendWhere(KeysColumns.TYPE + " = "); - qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); + case KEY_RING_USER_IDS: { + HashMap projectionMap = new HashMap(); + projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id"); + projectionMap.put(UserIds.MASTER_KEY_ID, UserIds.MASTER_KEY_ID); + projectionMap.put(UserIds.USER_ID, UserIds.USER_ID); + projectionMap.put(UserIds.RANK, UserIds.RANK); + projectionMap.put(UserIds.IS_PRIMARY, UserIds.IS_PRIMARY); + projectionMap.put(UserIds.VERIFIED, "0 AS " + UserIds.VERIFIED); + qb.setProjectionMap(projectionMap); - qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(2)); - - qb.appendWhere(" AND " + BaseColumns._ID + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); - - qb.setProjectionMap(getProjectionMapForKeys()); - - break; - - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID: - case PUBLIC_KEY_RING_USER_ID: - case SECRET_KEY_RING_USER_ID: - qb.setTables(Tables.USER_IDS - + " INNER JOIN " + Tables.KEY_RINGS + " ON (" - + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " - + Tables.USER_IDS + "." + KeysColumns.KEY_RING_ROW_ID - + ") LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.USER_IDS + "." + UserIds.KEY_RING_ROW_ID + " = " - + Tables.CERTS + "." + Certs.KEY_RING_ROW_ID - + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = " - + Tables.CERTS + "." + Certs.RANK - + ") LEFT JOIN " + Tables.KEYS + " ON (" - + Tables.KEYS + "." + Keys.KEY_ID + " = " - + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER - // might introduce a "trusted" flag later? for now, we simply assume - // every private key's signature is good. - + " AND " + Tables.KEYS + "." + Keys.TYPE - + " == " + KeyTypes.SECRET - + ")"); - - groupBy = Tables.USER_IDS + "." + UserIds.RANK; - - qb.setProjectionMap(getProjectionMapForUserIds()); - - if(match == PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID) { - qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(3)); - } else { - qb.appendWhere(Tables.USER_IDS + "." + UserIdsColumns.KEY_RING_ROW_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(2)); - } - - break; - - case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: - case SECRET_KEY_RING_USER_ID_BY_ROW_ID: qb.setTables(Tables.USER_IDS); - qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(2)); + qb.appendWhere(UserIds.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - qb.appendWhere(" AND " + BaseColumns._ID + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = UserIds.RANK + " ASC"; + } break; - case CERTS_BY_ROW_ID: - case CERTS_BY_KEY_ROW_ID_ALL: - case CERTS_BY_KEY_ROW_ID_HAS_SECRET: - case CERTS_BY_KEY_ROW_ID: + } + + case KEY_RINGS_PUBLIC: + case KEY_RING_PUBLIC: { + HashMap projectionMap = new HashMap(); + projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id"); + projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); + projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); + qb.setProjectionMap(projectionMap); + + qb.setTables(Tables.KEY_RINGS_PUBLIC); + + if(match == KEY_RING_PUBLIC) { + qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + } + + break; + } + + case KEY_RINGS_SECRET: + case KEY_RING_SECRET: { + HashMap projectionMap = new HashMap(); + projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id"); + projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); + projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); + qb.setProjectionMap(projectionMap); + + qb.setTables(Tables.KEY_RINGS_SECRET); + + if(match == KEY_RING_SECRET) { + qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + } + + break; + } + + case KEY_RING_CERTS: + HashMap projectionMap = new HashMap(); + projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID); + projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID); + projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK); + projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); + projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); + projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); + projectionMap.put(Certs.KEY_DATA, Tables.CERTS + "." + Certs.KEY_DATA); + // verified key data + projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); + // verifying key data + projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID); + qb.setProjectionMap(projectionMap); + qb.setTables(Tables.CERTS + " JOIN " + Tables.USER_IDS + " ON (" - + Tables.CERTS + "." + Certs.KEY_RING_ROW_ID + " = " - + Tables.USER_IDS + "." + UserIds.KEY_RING_ROW_ID + + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " AND " + Tables.CERTS + "." + Certs.RANK + " = " + Tables.USER_IDS + "." + UserIds.RANK - // noooooooot sure about this~ database design - + ")" + (match == CERTS_BY_KEY_ROW_ID_ALL ? " LEFT" : "") - + " JOIN " + Tables.KEYS + " ON (" - + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " - + Tables.KEYS + "." + Keys.KEY_ID - + (match == CERTS_BY_KEY_ROW_ID_HAS_SECRET ? - " AND " + Tables.KEYS + "." + Keys.TYPE + " = " + KeyTypes.SECRET : "" - ) + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON (" - + Tables.KEYS + "." + Keys.KEY_RING_ROW_ID + " = " - + "signer." + UserIds.KEY_RING_ROW_ID + + Tables.CERTS + "." + Keys.MASTER_KEY_ID + " = " + + "signer." + UserIds.MASTER_KEY_ID + " AND " + "signer." + Keys.RANK + " = 0" + ")"); - qb.setProjectionMap(getProjectionMapForCerts()); - groupBy = Tables.CERTS + "." + Certs.RANK + ", " + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER; - if(match == CERTS_BY_ROW_ID) { - qb.appendWhere(Tables.CERTS + "." + Certs._ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - } else { - qb.appendWhere(Tables.CERTS + "." + Certs.KEY_RING_ROW_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(2)); - } + qb.appendWhere(Tables.CERTS + "." + KeyRings.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); break; case API_APPS: qb.setTables(Tables.API_APPS); - break; - case API_APPS_BY_ROW_ID: - qb.setTables(Tables.API_APPS); - - qb.appendWhere(BaseColumns._ID + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); - break; case API_APPS_BY_PACKAGE_NAME: qb.setTables(Tables.API_APPS); qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(2)); + qb.appendWhereEscapeString(uri.getLastPathSegment()); break; + case API_ACCOUNTS: + qb.setTables(Tables.API_ACCOUNTS); + qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + break; + case API_ACCOUNTS_BY_ACCOUNT_NAME: + qb.setTables(Tables.API_ACCOUNTS); + qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + + qb.appendWhere(" AND " + Tables.API_ACCOUNTS + "." + ApiAccounts.ACCOUNT_NAME + " = "); + qb.appendWhereEscapeString(uri.getLastPathSegment()); + + break; default: - throw new IllegalArgumentException("Unknown URI " + uri); + throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } @@ -798,6 +499,7 @@ public class KeychainProvider extends ContentProvider { orderBy = sortOrder; } + SQLiteDatabase db = getDb().getReadableDatabase(); Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy); // Tell the cursor what uri to watch, so it knows when its source data changes @@ -821,78 +523,72 @@ public class KeychainProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")"); - final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); + final SQLiteDatabase db = getDb().getWritableDatabase(); Uri rowUri = null; - long rowId = -1; + Long keyId = null; try { final int match = mUriMatcher.match(uri); switch (match) { - case PUBLIC_KEY_RING: - values.put(KeyRings.TYPE, KeyTypes.PUBLIC); - - rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values); - rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); - + case KEY_RING_PUBLIC: + db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values); + keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); break; - case PUBLIC_KEY_RING_KEY: - values.put(Keys.TYPE, KeyTypes.PUBLIC); - - rowId = db.insertOrThrow(Tables.KEYS, null, values); - rowUri = Keys.buildPublicKeysUri(Long.toString(rowId)); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); + case KEY_RING_SECRET: + db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values); + keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); break; - case PUBLIC_KEY_RING_USER_ID: - rowId = db.insertOrThrow(Tables.USER_IDS, null, values); - rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId)); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); + case KEY_RING_KEYS: + Log.d(Constants.TAG, "keys"); + db.insertOrThrow(Tables.KEYS, null, values); + keyId = values.getAsLong(Keys.MASTER_KEY_ID); break; - case SECRET_KEY_RING: - values.put(KeyRings.TYPE, KeyTypes.SECRET); - - rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values); - rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); + case KEY_RING_USER_IDS: + db.insertOrThrow(Tables.USER_IDS, null, values); + keyId = values.getAsLong(UserIds.MASTER_KEY_ID); break; - case SECRET_KEY_RING_KEY: - values.put(Keys.TYPE, KeyTypes.SECRET); - - rowId = db.insertOrThrow(Tables.KEYS, null, values); - rowUri = Keys.buildSecretKeysUri(Long.toString(rowId)); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); + case KEY_RING_CERTS: + db.insertOrThrow(Tables.CERTS, null, values); + keyId = values.getAsLong(Certs.MASTER_KEY_ID); break; - case SECRET_KEY_RING_USER_ID: - rowId = db.insertOrThrow(Tables.USER_IDS, null, values); - rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId)); - break; case API_APPS: - rowId = db.insertOrThrow(Tables.API_APPS, null, values); - rowUri = ApiApps.buildIdUri(Long.toString(rowId)); + db.insertOrThrow(Tables.API_APPS, null, values); + break; + + case API_ACCOUNTS: + // set foreign key automatically based on given uri + // e.g., api_apps/com.example.app/accounts/ + String packageName = uri.getPathSegments().get(1); + values.put(ApiAccounts.PACKAGE_NAME, packageName); + + Log.d(Constants.TAG, "provider packageName: " + packageName); + + db.insertOrThrow(Tables.API_ACCOUNTS, null, values); + // TODO: this is wrong: +// rowUri = ApiAccounts.buildIdUri(Long.toString(rowId)); break; - case CERTS_BY_KEY_ROW_ID: - rowId = db.insertOrThrow(Tables.CERTS, null, values); - // kinda useless.. should this be buildCertsByKeyRowIdUri? - // rowUri = Certs.buildCertsUri(Long.toString(rowId)); - rowUri = uri; - break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } + if(keyId != null) { + uri = KeyRings.buildGenericKeyRingUri(keyId.toString()); + rowUri = uri; + } + // notify of changes in db getContext().getContentResolver().notifyChange(uri, null); } catch (SQLiteConstraintException e) { - Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?"); + Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?", e); } return rowUri; @@ -902,51 +598,43 @@ public class KeychainProvider extends ContentProvider { * {@inheritDoc} */ @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { + public int delete(Uri uri, String additionalSelection, String[] selectionArgs) { Log.v(Constants.TAG, "delete(uri=" + uri + ")"); - final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); + final SQLiteDatabase db = getDb().getWritableDatabase(); int count; final int match = mUriMatcher.match(uri); - String defaultSelection = null; switch (match) { - case PUBLIC_KEY_RING_BY_ROW_ID: - case SECRET_KEY_RING_BY_ROW_ID: - defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment(); + case KEY_RING_PUBLIC: { + @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above + String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); + if (!TextUtils.isEmpty(additionalSelection)) { + selection += " AND (" + additionalSelection + ")"; + } // corresponding keys and userIds are deleted by ON DELETE CASCADE - count = db.delete(Tables.KEY_RINGS, - buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), - selectionArgs); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); + count = db.delete(Tables.KEY_RINGS_PUBLIC, selection, selectionArgs); + uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1)); break; - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: - case SECRET_KEY_RING_BY_MASTER_KEY_ID: - defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment(); - // corresponding keys and userIds are deleted by ON DELETE CASCADE - count = db.delete(Tables.KEY_RINGS, - buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), - selectionArgs); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); - break; - case PUBLIC_KEY_RING_KEY_BY_ROW_ID: - case SECRET_KEY_RING_KEY_BY_ROW_ID: - count = db.delete(Tables.KEYS, - buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); - break; - case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: - case SECRET_KEY_RING_USER_ID_BY_ROW_ID: - count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection), - selectionArgs); - break; - case API_APPS_BY_ROW_ID: - count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection), - selectionArgs); + } + case KEY_RING_SECRET: { + @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above + String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); + if (!TextUtils.isEmpty(additionalSelection)) { + selection += " AND (" + additionalSelection + ")"; + } + count = db.delete(Tables.KEY_RINGS_SECRET, selection, selectionArgs); + uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1)); break; + } + case API_APPS_BY_PACKAGE_NAME: - count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection), + count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection), + selectionArgs); + break; + case API_ACCOUNTS_BY_ACCOUNT_NAME: + count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection), selectionArgs); break; default: @@ -966,58 +654,20 @@ public class KeychainProvider extends ContentProvider { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")"); - final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); + final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase(); String defaultSelection = null; int count = 0; try { final int match = mUriMatcher.match(uri); switch (match) { - case PUBLIC_KEY_RING_BY_ROW_ID: - case SECRET_KEY_RING_BY_ROW_ID: - defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment(); - - count = db.update( - Tables.KEY_RINGS, - values, - buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), - selection), selectionArgs); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); - - break; - case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: - case SECRET_KEY_RING_BY_MASTER_KEY_ID: - defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment(); - - count = db.update( - Tables.KEY_RINGS, - values, - buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), - selection), selectionArgs); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); - - break; - case PUBLIC_KEY_RING_KEY_BY_ROW_ID: - case SECRET_KEY_RING_KEY_BY_ROW_ID: - count = db - .update(Tables.KEYS, values, - buildDefaultKeysSelection(uri, getKeyType(match), selection), - selectionArgs); - sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); - - break; - case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: - case SECRET_KEY_RING_USER_ID_BY_ROW_ID: - count = db.update(Tables.USER_IDS, values, - buildDefaultUserIdsSelection(uri, selection), selectionArgs); - break; - case API_APPS_BY_ROW_ID: - count = db.update(Tables.API_APPS, values, - buildDefaultApiAppsSelection(uri, false, selection), selectionArgs); - break; case API_APPS_BY_PACKAGE_NAME: count = db.update(Tables.API_APPS, values, - buildDefaultApiAppsSelection(uri, true, selection), selectionArgs); + buildDefaultApiAppsSelection(uri, selection), selectionArgs); + break; + case API_ACCOUNTS_BY_ACCOUNT_NAME: + count = db.update(Tables.API_ACCOUNTS, values, + buildDefaultApiAccountsSelection(uri, selection), selectionArgs); break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); @@ -1033,81 +683,6 @@ public class KeychainProvider extends ContentProvider { return count; } - /** - * Build default selection statement for KeyRings. If no extra selection is specified only build - * where clause with rowId - * - * @param defaultSelection - * @param keyType - * @param selection - * @return - */ - private String buildDefaultKeyRingsSelection(String defaultSelection, Integer keyType, - String selection) { - String andType = ""; - if (keyType != null) { - andType = " AND " + KeyRingsColumns.TYPE + "=" + keyType; - } - - String andSelection = ""; - if (!TextUtils.isEmpty(selection)) { - andSelection = " AND (" + selection + ")"; - } - - return defaultSelection + andType + andSelection; - } - - /** - * Build default selection statement for Keys. If no extra selection is specified only build - * where clause with rowId - * - * @param uri - * @param selection - * @return - */ - private String buildDefaultKeysSelection(Uri uri, Integer keyType, String selection) { - String rowId = uri.getLastPathSegment(); - - String foreignKeyRingRowId = uri.getPathSegments().get(2); - String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = " - + foreignKeyRingRowId; - - String andType = ""; - if (keyType != null) { - andType = " AND " + KeysColumns.TYPE + "=" + keyType; - } - - String andSelection = ""; - if (!TextUtils.isEmpty(selection)) { - andSelection = " AND (" + selection + ")"; - } - - return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andType + andSelection; - } - - /** - * Build default selection statement for UserIds. If no extra selection is specified only build - * where clause with rowId - * - * @param uri - * @param selection - * @return - */ - private String buildDefaultUserIdsSelection(Uri uri, String selection) { - String rowId = uri.getLastPathSegment(); - - String foreignKeyRingRowId = uri.getPathSegments().get(2); - String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = " - + foreignKeyRingRowId; - - String andSelection = ""; - if (!TextUtils.isEmpty(selection)) { - andSelection = " AND (" + selection + ")"; - } - - return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection; - } - /** * Build default selection statement for API apps. If no extra selection is specified only build * where clause with rowId @@ -1116,43 +691,29 @@ public class KeychainProvider extends ContentProvider { * @param selection * @return */ - private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) { - String lastPathSegment = uri.getLastPathSegment(); + private String buildDefaultApiAppsSelection(Uri uri, String selection) { + String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment()); String andSelection = ""; if (!TextUtils.isEmpty(selection)) { andSelection = " AND (" + selection + ")"; } - if (packageSelection) { - return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection; - } else { - return BaseColumns._ID + "=" + lastPathSegment + andSelection; + return ApiApps.PACKAGE_NAME + "=" + packageName + andSelection; + } + + private String buildDefaultApiAccountsSelection(Uri uri, String selection) { + String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1)); + String accountName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment()); + + String andSelection = ""; + if (!TextUtils.isEmpty(selection)) { + andSelection = " AND (" + selection + ")"; } + + return ApiAccounts.PACKAGE_NAME + "=" + packageName + " AND " + + ApiAccounts.ACCOUNT_NAME + "=" + accountName + + andSelection; } - // @Override - // public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - // int match = mUriMatcher.match(uri); - // if (match != DATA_STREAM) { - // throw new FileNotFoundException(); - // } - // String fileName = uri.getLastPathSegment(); - // File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName); - // return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - // } - - /** - * This broadcast is send system wide to inform other application that a keyring was inserted, - * updated, or deleted - */ - private void sendBroadcastDatabaseChange(int keyType, String contentItemType) { - // TODO: Disabled, old API - // Intent intent = new Intent(); - // intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE); - // intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType); - // intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType); - // - // getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API); - } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java index da1bcb2d9..bc7de0b37 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobDatabase.java @@ -25,7 +25,7 @@ import android.provider.BaseColumns; import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns; public class KeychainServiceBlobDatabase extends SQLiteOpenHelper { - private static final String DATABASE_NAME = "apg_blob.db"; + private static final String DATABASE_NAME = "openkeychain_blob.db"; private static final int DATABASE_VERSION = 2; public static final String TABLE = "data"; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java index 6ac61e157..aa30e845d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainServiceBlobProvider.java @@ -38,7 +38,7 @@ import java.util.List; import java.util.UUID; public class KeychainServiceBlobProvider extends ContentProvider { - private static final String STORE_PATH = Constants.Path.APP_DIR + "/ApgBlobs"; + private static final String STORE_PATH = Constants.Path.APP_DIR + "/KeychainBlobs"; private KeychainServiceBlobDatabase mBlobDatabase = null; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 1d249c67e..503fed3c9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -19,27 +19,36 @@ package org.sufficientlysecure.keychain.provider; import java.security.SignatureException; import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.UserAttributePacket; -import org.spongycastle.bcpg.UserAttributeSubpacket; -import android.content.*; +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; import android.os.RemoteException; -import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.openpgp.*; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import org.sufficientlysecure.keychain.service.remote.AppSettings; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -49,27 +58,82 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Map; - +import java.util.HashSet; +import java.util.Set; public class ProviderHelper { - /** - * Private helper method to get PGPKeyRing from database - */ + // 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; + + public static Object getGenericData(Context context, Uri uri, String column, int type) { + return getGenericData(context, uri, new String[] { column }, new int[] { type }).get(column); + } + public static HashMap getGenericData(Context context, Uri uri, String[] proj, int[] types) { + Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null); + + HashMap 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; + } + } + + if (cursor != null) { + cursor.close(); + } + + return result; + } + + public static Object getUnifiedData(Context context, long masterKeyId, String column, int type) { + return getUnifiedData(context, masterKeyId, new String[] { column }, new int[] { type }).get(column); + } + public static HashMap getUnifiedData(Context context, long masterKeyId, String[] proj, int[] types) { + return getGenericData(context, KeyRings.buildUnifiedKeyRingUri(Long.toString(masterKeyId)), proj, types); + } + + public static long getMasterKeyId(Context context, Uri queryUri) { + // try extracting from the uri first + String firstSegment = queryUri.getPathSegments().get(1); + if(!firstSegment.equals("find")) try { + return Long.parseLong(firstSegment); + } catch(NumberFormatException e) { + // didn't work? oh well. + Log.d(Constants.TAG, "Couldn't get masterKeyId from URI, querying..."); + } + Object data = getGenericData(context, queryUri, KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER); + if(data != null) + return (Long) data; + // TODO better error handling? + return 0L; + } + public static Map getPGPKeyRings(Context context, Uri queryUri) { Cursor cursor = context.getContentResolver().query(queryUri, - new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.KEY_RING_DATA}, null, null, null); + new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA }, + null, null, null); Map result = new HashMap(cursor.getCount()); if (cursor != null && cursor.moveToFirst()) do { - int keyRingDataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA); - int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID); - - byte[] data = cursor.getBlob(keyRingDataCol); + long masterKeyId = cursor.getLong(0); + byte[] data = cursor.getBlob(1); if (data != null) { - result.put(cursor.getLong(masterKeyIdCol), PgpConversionHelper.BytesToPGPKeyRing(data)); + result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data)); } - } while(cursor.moveToNext()); if (cursor != null) { @@ -79,81 +143,45 @@ public class ProviderHelper { return result; } public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) { - return getPGPKeyRings(context, queryUri).values().iterator().next(); + Map result = getPGPKeyRings(context, queryUri); + if(result.isEmpty()) + return null; + return result.values().iterator().next(); } - /** - * Retrieves the actual PGPPublicKeyRing object from the database blob based on the rowId - */ - public static PGPPublicKeyRing getPGPPublicKeyRingByRowId(Context context, long rowId) { - Uri queryUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)); - return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri); + public static PGPPublicKeyRing getPGPPublicKeyRingWithKeyId(Context context, long keyId) { + Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)); + long masterKeyId = getMasterKeyId(context, uri); + if(masterKeyId != 0) + return getPGPPublicKeyRing(context, masterKeyId); + return null; + } + public static PGPSecretKeyRing getPGPSecretKeyRingWithKeyId(Context context, long keyId) { + Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)); + long masterKeyId = getMasterKeyId(context, uri); + if(masterKeyId != 0) + return getPGPSecretKeyRing(context, masterKeyId); + return null; } /** * Retrieves the actual PGPPublicKeyRing object from the database blob based on the masterKeyId */ - public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context, + public static PGPPublicKeyRing getPGPPublicKeyRing(Context context, long masterKeyId) { - Uri queryUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId)); + Uri queryUri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri); } - /** - * Retrieves the actual PGPPublicKeyRing object from the database blob associated with a key - * with this keyId - */ - public static PGPPublicKeyRing getPGPPublicKeyRingByKeyId(Context context, long keyId) { - Uri queryUri = KeyRings.buildPublicKeyRingsByKeyIdUri(Long.toString(keyId)); - return (PGPPublicKeyRing) getPGPKeyRing(context, queryUri); - } - - /** - * Retrieves the actual PGPPublicKey object from the database blob associated with a key with - * this keyId - */ - public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) { - PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId); - - return (keyRing == null) ? null : keyRing.getPublicKey(keyId); - } - - /** - * Retrieves the actual PGPSecretKeyRing object from the database blob based on the rowId - */ - public static PGPSecretKeyRing getPGPSecretKeyRingByRowId(Context context, long rowId) { - Uri queryUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)); - return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri); - } - /** * Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId */ - public static PGPSecretKeyRing getPGPSecretKeyRingByMasterKeyId(Context context, + public static PGPSecretKeyRing getPGPSecretKeyRing(Context context, long masterKeyId) { - Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId)); + Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)); return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri); } - /** - * Retrieves the actual PGPSecretKeyRing object from the database blob associated with a key - * with this keyId - */ - public static PGPSecretKeyRing getPGPSecretKeyRingByKeyId(Context context, long keyId) { - Uri queryUri = KeyRings.buildSecretKeyRingsByKeyIdUri(Long.toString(keyId)); - return (PGPSecretKeyRing) getPGPKeyRing(context, queryUri); - } - - /** - * Retrieves the actual PGPSecretKey object from the database blob associated with a key with - * this keyId - */ - public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) { - PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId); - - return (keyRing == null) ? null : keyRing.getSecretKey(keyId); - } - /** * Saves PGPPublicKeyRing with its keys and userIds in DB */ @@ -162,21 +190,12 @@ public class ProviderHelper { PGPPublicKey masterKey = keyRing.getPublicKey(); long masterKeyId = masterKey.getKeyID(); - Uri deleteUri = KeyRings.buildPublicKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId)); - - // get current _ID of key - long currentRowId = -1; - Cursor oldQuery = context.getContentResolver() - .query(deleteUri, new String[]{KeyRings._ID}, null, null, null); - if (oldQuery != null && oldQuery.moveToFirst()) { - currentRowId = oldQuery.getLong(0); - } else { - Log.e(Constants.TAG, "Key could not be found! Something wrong is happening!"); - } + // IF there is a secret key, preserve it! + PGPSecretKeyRing secretRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId); // delete old version of this keyRing, which also deletes all keys and userIds on cascade try { - context.getContentResolver().delete(deleteUri, null, null); + context.getContentResolver().delete(KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null); } catch (UnsupportedOperationException e) { Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e); } @@ -186,32 +205,28 @@ public class ProviderHelper { // NOTE: If we would not use the same _ID again, // getting back to the ViewKeyActivity would result in Nullpointer, // because the currently loaded key would be gone from the database - if (currentRowId != -1) { - values.put(KeyRings._ID, currentRowId); - } - values.put(KeyRings.MASTER_KEY_ID, masterKeyId); - values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); + values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); + values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); // insert new version of this keyRing - Uri uri = KeyRings.buildPublicKeyRingsUri(); + Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); Uri insertedUri = context.getContentResolver().insert(uri, values); - long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment()); // save all keys and userIds included in keyRing object in database ArrayList operations = new ArrayList(); int rank = 0; for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank)); + operations.add(buildPublicKeyOperations(context, masterKeyId, key, rank)); ++rank; } // get a list of owned secret keys, for verification filtering - Map allKeyRings = getPGPKeyRings(context, KeyRings.buildSecretKeyRingsUri()); + Map allKeyRings = getPGPKeyRings(context, KeyRingData.buildSecretKeyRingUri()); int userIdRank = 0; for (String userId : new IterableIterator(masterKey.getUserIDs())) { - operations.add(buildPublicUserIdOperations(context, keyRingRowId, userId, userIdRank)); + operations.add(buildUserIdOperations(context, masterKeyId, userId, userIdRank)); // look through signatures for this specific key for (PGPSignature cert : new IterableIterator( @@ -244,8 +259,8 @@ public class ProviderHelper { + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()) ); // regardless of verification, save the certification - operations.add(buildPublicCertOperations( - context, keyRingRowId, userIdRank, masterKey.getKeyID(), cert, verified)); + operations.add(buildCertOperations( + context, masterKeyId, userIdRank, masterKey.getKeyID(), cert, verified)); } ++userIdRank; @@ -258,99 +273,74 @@ public class ProviderHelper { } catch (OperationApplicationException e) { Log.e(Constants.TAG, "applyBatch failed!", e); } + + // Save the saved keyring (if any) + if(secretRing != null) { + saveKeyRing(context, secretRing); + } + } /** - * Saves PGPSecretKeyRing with its keys and userIds in DB + * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring + * is already in the database! */ @SuppressWarnings("unchecked") public static void saveKeyRing(Context context, PGPSecretKeyRing keyRing) throws IOException { - PGPSecretKey masterKey = keyRing.getSecretKey(); - long masterKeyId = masterKey.getKeyID(); - - Uri deleteUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId)); - - // get current _ID of key - long currentRowId = -1; - Cursor oldQuery = context.getContentResolver() - .query(deleteUri, new String[]{KeyRings._ID}, null, null, null); - if (oldQuery != null && oldQuery.moveToFirst()) { - currentRowId = oldQuery.getLong(0); - } else { - Log.e(Constants.TAG, "Key could not be found! Something wrong is happening!"); - } - - // delete old version of this keyRing, which also deletes all keys and userIds on cascade - try { - context.getContentResolver().delete(deleteUri, null, null); - } catch (UnsupportedOperationException e) { - Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e); - } + long masterKeyId = keyRing.getPublicKey().getKeyID(); + // save secret keyring ContentValues values = new ContentValues(); - // use exactly the same _ID again to replace key in-place. - // NOTE: If we would not use the same _ID again, - // getting back to the ViewKeyActivity would result in Nullpointer, - // because the currently loaded key would be gone from the database - if (currentRowId != -1) { - values.put(KeyRings._ID, currentRowId); - } - values.put(KeyRings.MASTER_KEY_ID, masterKeyId); - values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); - + values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); + values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); // insert new version of this keyRing - Uri uri = KeyRings.buildSecretKeyRingsUri(); - Uri insertedUri = context.getContentResolver().insert(uri, values); - long keyRingRowId = Long.valueOf(insertedUri.getLastPathSegment()); + Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)); + context.getContentResolver().insert(uri, values); - // save all keys and userIds included in keyRing object in database - ArrayList operations = new ArrayList(); + } - int rank = 0; - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - operations.add(buildSecretKeyOperations(context, keyRingRowId, key, rank)); - ++rank; - } + /** + * Saves (or updates) a pair of public and secret KeyRings in the database + */ + @SuppressWarnings("unchecked") + public static void saveKeyRing(Context context, PGPPublicKeyRing pubRing, PGPSecretKeyRing privRing) throws IOException { + long masterKeyId = pubRing.getPublicKey().getKeyID(); - int userIdRank = 0; - for (String userId : new IterableIterator(masterKey.getUserIDs())) { - operations.add(buildSecretUserIdOperations(context, keyRingRowId, userId, userIdRank)); - ++userIdRank; - } + // delete secret keyring (so it isn't unnecessarily saved by public-saveKeyRing below) + context.getContentResolver().delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null); - try { - context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); - } catch (RemoteException e) { - Log.e(Constants.TAG, "applyBatch failed!", e); - } catch (OperationApplicationException e) { - Log.e(Constants.TAG, "applyBatch failed!", e); - } + // save public keyring + saveKeyRing(context, pubRing); + saveKeyRing(context, privRing); } /** * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ private static ContentProviderOperation buildPublicKeyOperations(Context context, - long keyRingRowId, PGPPublicKey key, int rank) throws IOException { + long masterKeyId, PGPPublicKey key, int rank) throws IOException { + ContentValues values = new ContentValues(); + values.put(Keys.MASTER_KEY_ID, masterKeyId); + values.put(Keys.RANK, rank); + values.put(Keys.KEY_ID, key.getKeyID()); - values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); - values.put(Keys.ALGORITHM, key.getAlgorithm()); values.put(Keys.KEY_SIZE, key.getBitStrength()); - values.put(Keys.CAN_SIGN, PgpKeyHelper.isSigningKey(key)); + values.put(Keys.ALGORITHM, key.getAlgorithm()); + values.put(Keys.FINGERPRINT, key.getFingerprint()); + + values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key))); + values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key))); values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key)); values.put(Keys.IS_REVOKED, key.isRevoked()); + values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000); Date expiryDate = PgpKeyHelper.getExpiryDate(key); if (expiryDate != null) { values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); } - values.put(Keys.KEY_RING_ROW_ID, keyRingRowId); - values.put(Keys.KEY_DATA, key.getEncoded()); - values.put(Keys.RANK, rank); - values.put(Keys.FINGERPRINT, key.getFingerprint()); - Uri uri = Keys.buildPublicKeysUri(Long.toString(keyRingRowId)); + Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); return ContentProviderOperation.newInsert(uri).withValues(values).build(); } @@ -358,23 +348,23 @@ public class ProviderHelper { /** * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ - private static ContentProviderOperation buildPublicCertOperations(Context context, - long keyRingRowId, + private static ContentProviderOperation buildCertOperations(Context context, + long masterKeyId, int rank, long keyId, PGPSignature cert, boolean verified) throws IOException { ContentValues values = new ContentValues(); - values.put(Certs.KEY_RING_ROW_ID, keyRingRowId); + values.put(Certs.MASTER_KEY_ID, masterKeyId); values.put(Certs.RANK, rank); - values.put(Certs.KEY_ID, keyId); values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID()); values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000); + values.put(Certs.EXPIRY, (String) null); // TODO values.put(Certs.VERIFIED, verified); values.put(Certs.KEY_DATA, cert.getEncoded()); - Uri uri = Certs.buildCertsByKeyRowIdUri(Long.toString(keyRingRowId)); + Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId)); return ContentProviderOperation.newInsert(uri).withValues(values).build(); } @@ -382,351 +372,28 @@ public class ProviderHelper { /** * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing */ - private static ContentProviderOperation buildPublicUserIdOperations(Context context, - long keyRingRowId, String userId, int rank) { + private static ContentProviderOperation buildUserIdOperations(Context context, + long masterKeyId, String userId, int rank) { ContentValues values = new ContentValues(); - values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId); + values.put(UserIds.MASTER_KEY_ID, masterKeyId); values.put(UserIds.USER_ID, userId); values.put(UserIds.RANK, rank); - Uri uri = UserIds.buildPublicUserIdsUri(Long.toString(keyRingRowId)); + Uri uri = UserIds.buildUserIdsUri(Long.toString(masterKeyId)); return ContentProviderOperation.newInsert(uri).withValues(values).build(); } - /** - * Build ContentProviderOperation to add PGPSecretKey to database corresponding to a keyRing - */ - private static ContentProviderOperation buildSecretKeyOperations(Context context, - long keyRingRowId, PGPSecretKey key, int rank) throws IOException { - ContentValues values = new ContentValues(); - - boolean hasPrivate = true; - if (key.isMasterKey()) { - if (key.isPrivateKeyEmpty()) { - hasPrivate = false; - } - } - - values.put(Keys.KEY_ID, key.getKeyID()); - values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); - values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); - values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); - values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && hasPrivate)); - values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && hasPrivate)); - values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key)); - values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); - values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000); - Date expiryDate = PgpKeyHelper.getExpiryDate(key); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); - } - values.put(Keys.KEY_RING_ROW_ID, keyRingRowId); - values.put(Keys.KEY_DATA, key.getEncoded()); - values.put(Keys.RANK, rank); - values.put(Keys.FINGERPRINT, key.getPublicKey().getFingerprint()); - - Uri uri = Keys.buildSecretKeysUri(Long.toString(keyRingRowId)); - - return ContentProviderOperation.newInsert(uri).withValues(values).build(); - } - - /** - * Build ContentProviderOperation to add SecretUserIds to database corresponding to a keyRing - */ - private static ContentProviderOperation buildSecretUserIdOperations(Context context, - long keyRingRowId, String userId, int rank) { - ContentValues values = new ContentValues(); - values.put(UserIds.KEY_RING_ROW_ID, keyRingRowId); - values.put(UserIds.USER_ID, userId); - values.put(UserIds.RANK, rank); - - Uri uri = UserIds.buildSecretUserIdsUri(Long.toString(keyRingRowId)); - - return ContentProviderOperation.newInsert(uri).withValues(values).build(); - } - - /** - * Private helper method - */ - private static ArrayList getKeyRingsMasterKeyIds(Context context, Uri queryUri) { - Cursor cursor = context.getContentResolver().query(queryUri, - new String[]{KeyRings.MASTER_KEY_ID}, null, null, null); - - ArrayList masterKeyIds = new ArrayList(); - if (cursor != null) { - int masterKeyIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID); - if (cursor.moveToFirst()) { - do { - masterKeyIds.add(cursor.getLong(masterKeyIdCol)); - } while (cursor.moveToNext()); - } - } - - if (cursor != null) { - cursor.close(); - } - - return masterKeyIds; - } - - /** - * Private helper method - */ - private static ArrayList getKeyRingsRowIds(Context context, Uri queryUri) { - Cursor cursor = context.getContentResolver().query(queryUri, - new String[]{KeyRings._ID}, null, null, null); - - ArrayList rowIds = new ArrayList(); - if (cursor != null) { - int idCol = cursor.getColumnIndex(KeyRings._ID); - if (cursor.moveToFirst()) { - do { - rowIds.add(cursor.getLong(idCol)); - } while (cursor.moveToNext()); - } - } - - if (cursor != null) { - cursor.close(); - } - - return rowIds; - } - - /** - * Retrieves ids of all SecretKeyRings - */ - public static ArrayList getSecretKeyRingsMasterKeyIds(Context context) { - Uri queryUri = KeyRings.buildSecretKeyRingsUri(); - return getKeyRingsMasterKeyIds(context, queryUri); - } - - /** - * Retrieves ids of all PublicKeyRings - */ - public static ArrayList getPublicKeyRingsMasterKeyIds(Context context) { - Uri queryUri = KeyRings.buildPublicKeyRingsUri(); - return getKeyRingsMasterKeyIds(context, queryUri); - } - - /** - * Retrieves ids of all SecretKeyRings - */ - public static ArrayList getSecretKeyRingsRowIds(Context context) { - Uri queryUri = KeyRings.buildSecretKeyRingsUri(); - return getKeyRingsRowIds(context, queryUri); - } - - /** - * Retrieves ids of all PublicKeyRings - */ - public static ArrayList getPublicKeyRingsRowIds(Context context) { - Uri queryUri = KeyRings.buildPublicKeyRingsUri(); - return getKeyRingsRowIds(context, queryUri); - } - - public static void deletePublicKeyRing(Context context, long rowId) { - ContentResolver cr = context.getContentResolver(); - cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null); - } - - public static void deleteSecretKeyRing(Context context, long rowId) { - ContentResolver cr = context.getContentResolver(); - cr.delete(KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)), null, null); - } - - /** - * Get master key id of keyring by its row id - */ - public static long getPublicMasterKeyId(Context context, long keyRingRowId) { - Uri queryUri = KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowId)); - return getMasterKeyId(context, queryUri); - } - - /** - * Get empty status of master key of keyring by its row id - */ - public static boolean getSecretMasterKeyCanSign(Context context, long keyRingRowId) { - Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId)); - return getMasterKeyCanSign(context, queryUri); - } - - /** - * Private helper method to get master key private empty status of keyring by its row id - */ - public static boolean getMasterKeyCanSign(Context context, Uri queryUri) { - String[] projection = new String[]{ - KeyRings.MASTER_KEY_ID, - "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS - + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = " - + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID - + " AND sign_keys." + Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY - + " = 1) AS sign", }; - - ContentResolver cr = context.getContentResolver(); - Cursor cursor = cr.query(queryUri, projection, null, null, null); - - long masterKeyId = -1; - if (cursor != null && cursor.moveToFirst()) { - int masterKeyIdCol = cursor.getColumnIndex("sign"); - - masterKeyId = cursor.getLong(masterKeyIdCol); - } - - if (cursor != null) { - cursor.close(); - } - - return (masterKeyId > 0); - } - - public static boolean hasSecretKeyByMasterKeyId(Context context, long masterKeyId) { - Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId)); - // see if we can get our master key id back from the uri - return getMasterKeyId(context, queryUri) == masterKeyId; - } - - /** - * Get master key id of keyring by its row id - */ - public static long getSecretMasterKeyId(Context context, long keyRingRowId) { - Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId)); - return getMasterKeyId(context, queryUri); - } - - /** - * Get master key id of key - */ - public static long getMasterKeyId(Context context, Uri queryUri) { - String[] projection = new String[]{KeyRings.MASTER_KEY_ID}; - Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null); - - long masterKeyId = 0; - try { - if (cursor != null && cursor.moveToFirst()) { - int masterKeyIdCol = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID); - - masterKeyId = cursor.getLong(masterKeyIdCol); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return masterKeyId; - } - - public static long getRowId(Context context, Uri queryUri) { - String[] projection = new String[]{KeyRings._ID}; - Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null); - - long rowId = 0; - try { - if (cursor != null && cursor.moveToFirst()) { - int idCol = cursor.getColumnIndexOrThrow(KeyRings._ID); - - rowId = cursor.getLong(idCol); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return rowId; - } - - /** - * Get fingerprint of key - */ - public static byte[] getFingerprint(Context context, Uri queryUri) { - String[] projection = new String[]{Keys.FINGERPRINT}; - Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null); - - byte[] fingerprint = null; - try { - if (cursor != null && cursor.moveToFirst()) { - int col = cursor.getColumnIndexOrThrow(Keys.FINGERPRINT); - - fingerprint = cursor.getBlob(col); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - // FALLBACK: If fingerprint is not in database, get it from key blob! - // this could happen if the key was saved by a previous version of Keychain! - if (fingerprint == null) { - Log.d(Constants.TAG, "FALLBACK: fingerprint is not in database, get it from key blob!"); - - // get master key id - projection = new String[]{KeyRings.MASTER_KEY_ID}; - cursor = context.getContentResolver().query(queryUri, projection, null, null, null); - long masterKeyId = 0; - try { - if (cursor != null && cursor.moveToFirst()) { - int col = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID); - - masterKeyId = cursor.getLong(col); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, masterKeyId); - // if it is no public key get it from your own keys... - if (key == null) { - PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, masterKeyId); - if (secretKey == null) { - Log.e(Constants.TAG, "Key could not be found!"); - return null; - } - key = secretKey.getPublicKey(); - } - - fingerprint = key.getFingerprint(); - } - - return fingerprint; - } - - public static String getUserId(Context context, Uri queryUri) { - String[] projection = new String[]{UserIds.USER_ID}; - Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null); - - String userId = null; - try { - if (cursor != null && cursor.moveToFirst()) { - int col = cursor.getColumnIndexOrThrow(UserIds.USER_ID); - - userId = cursor.getString(col); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return userId; - } - - public static ArrayList getKeyRingsAsArmoredString(Context context, Uri uri, - long[] masterKeyIds) { + public static ArrayList getKeyRingsAsArmoredString(Context context, long[] masterKeyIds) { ArrayList output = new ArrayList(); if (masterKeyIds != null && masterKeyIds.length > 0) { - Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds); + Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, masterKeyIds); if (cursor != null) { - int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID); - int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA); + int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID); + int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA); if (cursor.moveToFirst()) { do { Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol)); @@ -776,48 +443,11 @@ public class ProviderHelper { return null; } } - - public static byte[] getKeyRingsAsByteArray(Context context, Uri uri, long[] masterKeyIds) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - if (masterKeyIds != null && masterKeyIds.length > 0) { - - Cursor cursor = getCursorWithSelectedKeyringMasterKeyIds(context, uri, masterKeyIds); - - if (cursor != null) { - int masterIdCol = cursor.getColumnIndex(KeyRings.MASTER_KEY_ID); - int dataCol = cursor.getColumnIndex(KeyRings.KEY_RING_DATA); - if (cursor.moveToFirst()) { - do { - Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol)); - - // get actual keyring data blob and write it to ByteArrayOutputStream - try { - bos.write(cursor.getBlob(dataCol)); - } catch (IOException e) { - Log.e(Constants.TAG, "IOException", e); - } - } while (cursor.moveToNext()); - } - } - - if (cursor != null) { - cursor.close(); - } - - } else { - Log.e(Constants.TAG, "No master keys given!"); - } - - return bos.toByteArray(); - } - - private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, Uri baseUri, - long[] masterKeyIds) { + private static Cursor getCursorWithSelectedKeyringMasterKeyIds(Context context, long[] masterKeyIds) { Cursor cursor = null; if (masterKeyIds != null && masterKeyIds.length > 0) { - String inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN ("; + String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN ("; for (int i = 0; i < masterKeyIds.length; ++i) { if (i != 0) { inMasterKeyList += ", "; @@ -826,9 +456,9 @@ public class ProviderHelper { } inMasterKeyList += ")"; - cursor = context.getContentResolver().query(baseUri, - new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.KEY_RING_DATA}, - inMasterKeyList, null, null); + cursor = context.getContentResolver().query(KeyRingData.buildPublicKeyRingUri(), new String[] { + KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA + }, inMasterKeyList, null, null); } return cursor; @@ -859,19 +489,28 @@ public class ProviderHelper { ContentValues values = new ContentValues(); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature()); - values.put(ApiApps.KEY_ID, appSettings.getKeyId()); - values.put(ApiApps.COMPRESSION, appSettings.getCompression()); - values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm()); - values.put(ApiApps.HASH_ALORITHM, appSettings.getHashAlgorithm()); + return values; + } + private static ContentValues contentValueForApiAccounts(AccountSettings accSettings) { + ContentValues values = new ContentValues(); + values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName()); + values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId()); + values.put(KeychainContract.ApiAccounts.COMPRESSION, accSettings.getCompression()); + values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, accSettings.getEncryptionAlgorithm()); + values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, accSettings.getHashAlgorithm()); return values; } public static void insertApiApp(Context context, AppSettings appSettings) { - context.getContentResolver().insert(ApiApps.CONTENT_URI, + context.getContentResolver().insert(KeychainContract.ApiApps.CONTENT_URI, contentValueForApiApps(appSettings)); } + public static void insertApiAccount(Context context, Uri uri, AccountSettings accSettings) { + context.getContentResolver().insert(uri, contentValueForApiAccounts(accSettings)); + } + public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) { if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null, null) <= 0) { @@ -879,30 +518,73 @@ public class ProviderHelper { } } + public static void updateApiAccount(Context context, AccountSettings accSettings, Uri uri) { + if (context.getContentResolver().update(uri, contentValueForApiAccounts(accSettings), null, + null) <= 0) { + throw new RuntimeException(); + } + } + + /** + * Must be an uri pointing to an account + * + * @param context + * @param uri + * @return + */ public static AppSettings getApiAppSettings(Context context, Uri uri) { AppSettings settings = null; Cursor cur = context.getContentResolver().query(uri, null, null, null, null); if (cur != null && cur.moveToFirst()) { settings = new AppSettings(); - settings.setPackageName(cur.getString(cur - .getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); - settings.setPackageSignature(cur.getBlob(cur - .getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); - settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID))); - settings.setCompression(cur.getInt(cur - .getColumnIndexOrThrow(KeychainContract.ApiApps.COMPRESSION))); - settings.setHashAlgorithm(cur.getInt(cur - .getColumnIndexOrThrow(KeychainContract.ApiApps.HASH_ALORITHM))); - settings.setEncryptionAlgorithm(cur.getInt(cur - .getColumnIndexOrThrow(KeychainContract.ApiApps.ENCRYPTION_ALGORITHM))); + settings.setPackageName(cur.getString( + cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); + settings.setPackageSignature(cur.getBlob( + cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); } return settings; } + public static AccountSettings getApiAccountSettings(Context context, Uri accountUri) { + AccountSettings settings = null; + + Cursor cur = context.getContentResolver().query(accountUri, null, null, null, null); + if (cur != null && cur.moveToFirst()) { + settings = new AccountSettings(); + + settings.setAccountName(cur.getString( + cur.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME))); + settings.setKeyId(cur.getLong( + cur.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID))); + settings.setCompression(cur.getInt( + cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.COMPRESSION))); + settings.setHashAlgorithm(cur.getInt( + cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.HASH_ALORITHM))); + settings.setEncryptionAlgorithm(cur.getInt( + cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM))); + } + + return settings; + } + + public static Set getAllKeyIdsForApp(Context context, Uri uri) { + Set keyIds = new HashSet(); + + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor != null) { + int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID); + while (cursor.moveToNext()) { + keyIds.add(cursor.getLong(keyIdColumn)); + } + } + + return keyIds; + } + public static byte[] getApiAppSignature(Context context, String packageName) { - Uri queryUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName); + Uri queryUri = ApiApps.buildByPackageNameUri(packageName); String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE}; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettings.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java similarity index 73% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettings.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java index 6f2d67efb..832cbc752 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettings.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/AccountSettings.java @@ -15,48 +15,39 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Id; -public class AppSettings { - private String mPackageName; - private byte[] mPackageSignature; +public class AccountSettings { + private String mAccountName; private long mKeyId = Id.key.none; private int mEncryptionAlgorithm; private int mHashAlgorithm; private int mCompression; - public AppSettings() { + public AccountSettings() { } - public AppSettings(String packageName, byte[] packageSignature) { + public AccountSettings(String accountName) { super(); - this.mPackageName = packageName; - this.mPackageSignature = packageSignature; + this.mAccountName = accountName; + // defaults: this.mEncryptionAlgorithm = PGPEncryptedData.AES_256; this.mHashAlgorithm = HashAlgorithmTags.SHA512; this.mCompression = Id.choice.compression.zlib; } - public String getPackageName() { - return mPackageName; + public String getAccountName() { + return mAccountName; } - public void setPackageName(String packageName) { - this.mPackageName = packageName; - } - - public byte[] getPackageSignature() { - return mPackageSignature; - } - - public void setPackageSignature(byte[] packageSignature) { - this.mPackageSignature = packageSignature; + public void setAccountName(String mAccountName) { + this.mAccountName = mAccountName; } public long getKeyId() { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java new file mode 100644 index 000000000..a3f9f84c9 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.remote; + +public class AppSettings { + private String mPackageName; + private byte[] mPackageSignature; + + public AppSettings() { + + } + + public AppSettings(String packageName, byte[] packageSignature) { + super(); + this.mPackageName = packageName; + this.mPackageSignature = packageSignature; + } + + public String getPackageName() { + return mPackageName; + } + + public void setPackageName(String packageName) { + this.mPackageName = packageName; + } + + public byte[] getPackageSignature() { + return mPackageSignature; + } + + public void setPackageSignature(byte[] packageSignature) { + this.mPackageSignature = packageSignature; + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java similarity index 79% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 95dc897f0..b38fea5a9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote; import android.app.PendingIntent; import android.content.Intent; @@ -23,6 +23,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.IBinder; import android.os.ParcelFileDescriptor; + import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpSignatureResult; @@ -34,22 +35,22 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.ImportKeysActivity; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Set; public class OpenPgpService extends RemoteService { - private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551; - private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552; - private static final int PRIVATE_REQUEST_CODE_GET_KEYS = 553; - /** * Search database for key ids based on emails. * @@ -61,15 +62,15 @@ public class OpenPgpService extends RemoteService { ArrayList keyIds = new ArrayList(); boolean missingUserIdsCheck = false; - boolean dublicateUserIdsCheck = false; + boolean duplicateUserIdsCheck = false; ArrayList missingUserIds = new ArrayList(); - ArrayList dublicateUserIds = new ArrayList(); + ArrayList duplicateUserIds = new ArrayList(); for (String email : encryptionUserIds) { - Uri uri = KeychainContract.KeyRings.buildPublicKeyRingsByEmailsUri(email); + Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email); Cursor cur = getContentResolver().query(uri, null, null, null, null); if (cur.moveToFirst()) { - long id = cur.getLong(cur.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID)); + long id = cur.getLong(cur.getColumnIndex(KeyRings.MASTER_KEY_ID)); keyIds.add(id); } else { missingUserIdsCheck = true; @@ -77,8 +78,8 @@ public class OpenPgpService extends RemoteService { Log.d(Constants.TAG, "user id missing"); } if (cur.moveToNext()) { - dublicateUserIdsCheck = true; - dublicateUserIds.add(email); + duplicateUserIdsCheck = true; + duplicateUserIds.add(email); Log.d(Constants.TAG, "more than one user id with the same email"); } } @@ -90,17 +91,18 @@ public class OpenPgpService extends RemoteService { } // allow the user to verify pub key selection - if (missingUserIdsCheck || dublicateUserIdsCheck) { + if (missingUserIdsCheck || duplicateUserIdsCheck) { // build PendingIntent Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS); intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds); - intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds); + intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, duplicateUserIds); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); - PendingIntent pi = PendingIntent.getActivity - (getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); // return PendingIntent to be executed by client Intent result = new Intent(); @@ -126,8 +128,9 @@ public class OpenPgpService extends RemoteService { intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId); // pass params through to activity that it can be returned again later to repeat pgp operation intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); - PendingIntent pi = PendingIntent.getActivity - (getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); // return PendingIntent to be executed by client Intent result = new Intent(); @@ -137,7 +140,7 @@ public class OpenPgpService extends RemoteService { } private Intent signImpl(Intent data, ParcelFileDescriptor input, - ParcelFileDescriptor output, AppSettings appSettings) { + ParcelFileDescriptor output, AccountSettings accSettings) { try { boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); @@ -146,11 +149,11 @@ public class OpenPgpService extends RemoteService { if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); } else { - passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId()); + passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId()); } if (passphrase == null) { // get PendingIntent for passphrase input, add it to given params and return to client - Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); + Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId()); return passphraseBundle; } @@ -164,9 +167,9 @@ public class OpenPgpService extends RemoteService { // sign-only PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os); builder.enableAsciiArmorOutput(asciiArmor) - .signatureHashAlgorithm(appSettings.getHashAlgorithm()) + .signatureHashAlgorithm(accSettings.getHashAlgorithm()) .signatureForceV3(false) - .signatureKeyId(appSettings.getKeyId()) + .signatureKeyId(accSettings.getKeyId()) .signaturePassphrase(passphrase); builder.build().execute(); } finally { @@ -187,7 +190,8 @@ public class OpenPgpService extends RemoteService { } private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input, - ParcelFileDescriptor output, AppSettings appSettings, boolean sign) { + ParcelFileDescriptor output, AccountSettings accSettings, + boolean sign) { try { boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); @@ -210,14 +214,14 @@ public class OpenPgpService extends RemoteService { Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, - "Missing parameter user_ids or key_ids!")); + "Missing parameter user_ids or key_ids!")); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; } // add own key for encryption keyIds = Arrays.copyOf(keyIds, keyIds.length + 1); - keyIds[keyIds.length - 1] = appSettings.getKeyId(); + keyIds[keyIds.length - 1] = accSettings.getKeyId(); // build InputData and write into OutputStream // Get Input- and OutputStream from ParcelFileDescriptor @@ -229,8 +233,8 @@ public class OpenPgpService extends RemoteService { PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os); builder.enableAsciiArmorOutput(asciiArmor) - .compressionId(appSettings.getCompression()) - .symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm()) + .compressionId(accSettings.getCompression()) + .symmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm()) .encryptionKeyIds(keyIds); if (sign) { @@ -239,18 +243,18 @@ public class OpenPgpService extends RemoteService { passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); } else { passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), - appSettings.getKeyId()); + accSettings.getKeyId()); } if (passphrase == null) { // get PendingIntent for passphrase input, add it to given params and return to client - Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); + Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId()); return passphraseBundle; } // sign and encrypt - builder.signatureHashAlgorithm(appSettings.getHashAlgorithm()) + builder.signatureHashAlgorithm(accSettings.getHashAlgorithm()) .signatureForceV3(false) - .signatureKeyId(appSettings.getKeyId()) + .signatureKeyId(accSettings.getKeyId()) .signaturePassphrase(passphrase); } else { // encrypt only @@ -276,7 +280,7 @@ public class OpenPgpService extends RemoteService { } private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input, - ParcelFileDescriptor output, AppSettings appSettings) { + ParcelFileDescriptor output, Set allowedKeyIds) { try { // Get Input- and OutputStream from ParcelFileDescriptor InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); @@ -290,19 +294,21 @@ public class OpenPgpService extends RemoteService { InputData inputData = new InputData(is, inputLength); PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os); - builder.assumeSymmetric(false) // no support for symmetric encryption - // allow only the private key for this app for decryption - .enforcedKeyId(appSettings.getKeyId()) + builder.allowSymmetricDecryption(false) // no support for symmetric encryption + .allowedKeyIds(allowedKeyIds) // allow only private keys associated with + // accounts of this app .passphrase(passphrase); // TODO: currently does not support binary signed-only content PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute(); - if (decryptVerifyResult.isKeyPassphraseNeeded()) { + if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { // get PendingIntent for passphrase input, add it to given params and return to client - Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); + Intent passphraseBundle = + getPassphraseBundleIntent(data, decryptVerifyResult.getKeyIdPassphraseNeeded()); return passphraseBundle; - } else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) { + } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED == + decryptVerifyResult.getStatus()) { throw new PgpGeneralException("Decryption of symmetric content not supported by API!"); } @@ -311,14 +317,14 @@ public class OpenPgpService extends RemoteService { if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY) { // If signature is unknown we return an _additional_ PendingIntent // to retrieve the missing key - // TODO!!! - Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); - intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE); - intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo"); - intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); + Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN); + intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId()); + intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); - PendingIntent pi = PendingIntent.getActivity(getBaseContext(), - PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); result.putExtra(OpenPgpApi.RESULT_INTENT, pi); } @@ -346,19 +352,19 @@ public class OpenPgpService extends RemoteService { try { long keyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0); - if (ProviderHelper.getPGPPublicKeyByKeyId(this, keyId) == null) { + if (ProviderHelper.getPGPPublicKeyRing(this, keyId) == null) { Intent result = new Intent(); // If keys are not in db we return an additional PendingIntent // to retrieve the missing key - // TODO!!! - Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); - intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE); - intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo"); - intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); + Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN); + intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, keyId); + intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); - PendingIntent pi = PendingIntent.getActivity(getBaseContext(), - PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); @@ -366,6 +372,9 @@ public class OpenPgpService extends RemoteService { } else { Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + + // TODO: also return PendingIntent that opens the key view activity + return result; } } catch (Exception e) { @@ -407,7 +416,7 @@ public class OpenPgpService extends RemoteService { if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) { Intent result = new Intent(); OpenPgpError error = new OpenPgpError - (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!"); + (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!"); result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; @@ -432,17 +441,30 @@ public class OpenPgpService extends RemoteService { return errorResult; } - final AppSettings appSettings = getAppSettings(); + String accName; + if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) { + accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME); + } else { + accName = "default"; + } + final AccountSettings accSettings = getAccSettings(accName); + if (accSettings == null) { + return getCreateAccountIntent(data, accName); + } String action = data.getAction(); if (OpenPgpApi.ACTION_SIGN.equals(action)) { - return signImpl(data, input, output, appSettings); + return signImpl(data, input, output, accSettings); } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { - return encryptAndSignImpl(data, input, output, appSettings, false); + return encryptAndSignImpl(data, input, output, accSettings, false); } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { - return encryptAndSignImpl(data, input, output, appSettings, true); + return encryptAndSignImpl(data, input, output, accSettings, true); } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { - return decryptAndVerifyImpl(data, input, output, appSettings); + String currentPkg = getCurrentCallingPackage(); + Set allowedKeyIds = + ProviderHelper.getAllKeyIdsForApp(mContext, + ApiAccounts.buildBaseUri(currentPkg)); + return decryptAndVerifyImpl(data, input, output, allowedKeyIds); } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { return getKeyImpl(data); } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java similarity index 71% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java index 6a883316a..16a800022 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote; import android.app.PendingIntent; import android.app.Service; @@ -27,12 +27,14 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; + import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -44,10 +46,6 @@ import java.util.Arrays; public abstract class RemoteService extends Service { Context mContext; - private static final int PRIVATE_REQUEST_CODE_REGISTER = 651; - private static final int PRIVATE_REQUEST_CODE_ERROR = 652; - - public Context getContext() { return mContext; } @@ -55,13 +53,10 @@ public abstract class RemoteService extends Service { protected Intent isAllowed(Intent data) { try { if (isCallerAllowed(false)) { - return null; } else { - String[] callingPackages = getPackageManager().getPackagesForUid( - Binder.getCallingUid()); - // TODO: currently simply uses first entry - String packageName = callingPackages[0]; + String packageName = getCurrentCallingPackage(); + Log.d(Constants.TAG, "isAllowed packageName: " + packageName); byte[] packageSignature; try { @@ -83,8 +78,9 @@ public abstract class RemoteService extends Service { intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); - PendingIntent pi = PendingIntent.getActivity(getBaseContext(), - PRIVATE_REQUEST_CODE_REGISTER, intent, 0); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); // return PendingIntent to be executed by client Intent result = new Intent(); @@ -99,11 +95,12 @@ public abstract class RemoteService extends Service { Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE); intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, - getString(R.string.api_error_wrong_signature)); + getString(R.string.api_error_wrong_signature)); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); - PendingIntent pi = PendingIntent.getActivity(getBaseContext(), - PRIVATE_REQUEST_CODE_ERROR, intent, 0); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); // return PendingIntent to be executed by client Intent result = new Intent(); @@ -125,27 +122,57 @@ public abstract class RemoteService extends Service { } /** - * Retrieves AppSettings from database for the application calling this remote service + * Returns package name associated with the UID, which is assigned to the process that sent you the + * current transaction that is being processed :) + * + * @return package name + */ + protected String getCurrentCallingPackage() { + // TODO: + // callingPackages contains more than one entry when sharedUserId has been used... + String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + String currentPkg = callingPackages[0]; + Log.d(Constants.TAG, "currentPkg: " + currentPkg); + + return currentPkg; + } + + /** + * Retrieves AccountSettings from database for the application calling this remote service * * @return */ - protected AppSettings getAppSettings() { - String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + protected AccountSettings getAccSettings(String accountName) { + String currentPkg = getCurrentCallingPackage(); + Log.d(Constants.TAG, "accountName: " + accountName); - // get app settings for this package - for (int i = 0; i < callingPackages.length; i++) { - String currentPkg = callingPackages[i]; + Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName); - Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg); + AccountSettings settings = ProviderHelper.getApiAccountSettings(this, uri); - AppSettings settings = ProviderHelper.getApiAppSettings(this, uri); + return settings; // can be null! + } - if (settings != null) { - return settings; - } - } + protected Intent getCreateAccountIntent(Intent data, String accountName) { + String packageName = getCurrentCallingPackage(); + Log.d(Constants.TAG, "accountName: " + accountName); - return null; + Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); + intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT); + intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(RemoteServiceActivity.EXTRA_ACC_NAME, accountName); + intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); + + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, + intent, + PendingIntent.FLAG_CANCEL_CURRENT); + + // return PendingIntent to be executed by client + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); + result.putExtra(OpenPgpApi.RESULT_INTENT, pi); + + return result; } /** @@ -180,7 +207,7 @@ public abstract class RemoteService extends Service { } } - Log.d(Constants.TAG, "Caller is NOT allowed!"); + Log.d(Constants.TAG, "Uid is NOT allowed!"); return false; } @@ -192,7 +219,7 @@ public abstract class RemoteService extends Service { * @throws WrongPackageSignatureException */ private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException { - Log.d(Constants.TAG, "packageName: " + packageName); + Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName); ArrayList allowedPkgs = ProviderHelper.getRegisteredApiApps(this); Log.d(Constants.TAG, "allowed: " + allowedPkgs); @@ -216,10 +243,12 @@ public abstract class RemoteService extends Service { return true; } else { throw new WrongPackageSignatureException( - "PACKAGE NOT ALLOWED! Signature wrong! (Signature not equals signature from database)"); + "PACKAGE NOT ALLOWED! Signature wrong! (Signature not " + + "equals signature from database)"); } } + Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName); return false; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/WrongPackageSignatureException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/WrongPackageSignatureException.java similarity index 94% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/WrongPackageSignatureException.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/WrongPackageSignatureException.java index 0b642086a..6f44a65e9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/WrongPackageSignatureException.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/WrongPackageSignatureException.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote; public class WrongPackageSignatureException extends Exception { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java similarity index 63% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index 2ef170dec..671a3e0aa 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote.ui; import android.content.Intent; import android.net.Uri; @@ -24,16 +24,18 @@ import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.util.Log; -public class AppSettingsActivity extends ActionBarActivity { - private Uri mAppUri; +public class AccountSettingsActivity extends ActionBarActivity { + private Uri mAccountUri; - private AppSettingsFragment mSettingsFragment; + private AccountSettingsFragment mAccountSettingsFragment; @Override protected void onCreate(Bundle savedInstanceState) { @@ -50,57 +52,58 @@ public class AppSettingsActivity extends ActionBarActivity { } }); - setContentView(R.layout.api_app_settings_activity); + setContentView(R.layout.api_account_settings_activity); - mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_app_settings_fragment); + mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_account_settings_fragment); Intent intent = getIntent(); - mAppUri = intent.getData(); - if (mAppUri == null) { + mAccountUri = intent.getData(); + if (mAccountUri == null) { Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!"); finish(); return; } else { - Log.d(Constants.TAG, "uri: " + mAppUri); - loadData(mAppUri); + Log.d(Constants.TAG, "uri: " + mAccountUri); + loadData(savedInstanceState, mAccountUri); } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.api_app_settings, menu); + getMenuInflater().inflate(R.menu.api_account_settings, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_api_settings_revoke: - revokeAccess(); + case R.id.menu_account_settings_delete: + deleteAccount(); return true; - case R.id.menu_api_settings_cancel: + case R.id.menu_account_settings_cancel: finish(); return true; } return super.onOptionsItemSelected(item); } - private void loadData(Uri appUri) { - AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri); - mSettingsFragment.setAppSettings(settings); + private void loadData(Bundle savedInstanceState, Uri accountUri) { + // TODO: load this also like other fragment with newInstance arguments? + AccountSettings settings = ProviderHelper.getApiAccountSettings(this, accountUri); + mAccountSettingsFragment.setAccSettings(settings); } - private void revokeAccess() { - if (getContentResolver().delete(mAppUri, null, null) <= 0) { + private void deleteAccount() { + if (getContentResolver().delete(mAccountUri, null, null) <= 0) { throw new RuntimeException(); } finish(); } private void save() { - ProviderHelper.updateApiApp(this, mSettingsFragment.getAppSettings(), mAppUri); + ProviderHelper.updateApiAccount(this, mAccountSettingsFragment.getAccSettings(), mAccountUri); finish(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java new file mode 100644 index 000000000..992aa7c95 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Spinner; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.ui.EditKeyActivity; +import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment; +import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter; +import org.sufficientlysecure.keychain.util.AlgorithmNames; + +public class AccountSettingsFragment extends Fragment implements + SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { + + private static final int REQUEST_CODE_CREATE_KEY = 0x00008884; + + // model + private AccountSettings mAccSettings; + + // view + private TextView mAccNameView; + private Spinner mEncryptionAlgorithm; + private Spinner mHashAlgorithm; + private Spinner mCompression; + + private SelectSecretKeyLayoutFragment mSelectKeyFragment; + private BootstrapButton mCreateKeyButton; + + KeyValueSpinnerAdapter mEncryptionAdapter; + KeyValueSpinnerAdapter mHashAdapter; + KeyValueSpinnerAdapter mCompressionAdapter; + + public AccountSettings getAccSettings() { + return mAccSettings; + } + + public void setAccSettings(AccountSettings accountSettings) { + this.mAccSettings = accountSettings; + + mAccNameView.setText(accountSettings.getAccountName()); + mSelectKeyFragment.selectKey(accountSettings.getKeyId()); + mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(accountSettings + .getEncryptionAlgorithm())); + mHashAlgorithm.setSelection(mHashAdapter.getPosition(accountSettings.getHashAlgorithm())); + mCompression.setSelection(mCompressionAdapter.getPosition(accountSettings.getCompression())); + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.api_account_settings_fragment, container, false); + initView(view); + return view; + } + + /** + * Set error String on key selection + * + * @param error + */ + public void setErrorOnSelectKeyFragment(String error) { + mSelectKeyFragment.setError(error); + } + + private void initView(View view) { + mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById( + R.id.api_account_settings_select_key_fragment); + mSelectKeyFragment.setCallback(this); + + mAccNameView = (TextView) view.findViewById(R.id.api_account_settings_acc_name); + mEncryptionAlgorithm = (Spinner) view + .findViewById(R.id.api_account_settings_encryption_algorithm); + mHashAlgorithm = (Spinner) view.findViewById(R.id.api_account_settings_hash_algorithm); + mCompression = (Spinner) view.findViewById(R.id.api_account_settings_compression); + mCreateKeyButton = (BootstrapButton) view.findViewById(R.id.api_account_settings_create_key); + + mCreateKeyButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createKey(); + } + }); + + AlgorithmNames algorithmNames = new AlgorithmNames(getActivity()); + + mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(), + algorithmNames.getEncryptionNames()); + mEncryptionAlgorithm.setAdapter(mEncryptionAdapter); + mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mAccSettings.setEncryptionAlgorithm((int) id); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames()); + mHashAlgorithm.setAdapter(mHashAdapter); + mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mAccSettings.setHashAlgorithm((int) id); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(), + algorithmNames.getCompressionNames()); + mCompression.setAdapter(mCompressionAdapter); + mCompression.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mAccSettings.setCompression((int) id); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + } + + private void createKey() { + Intent intent = new Intent(getActivity(), EditKeyActivity.class); + intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); + intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); + // set default user id to account name TODO: not working currently in EditKey + intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, mAccSettings.getAccountName()); + startActivityForResult(intent, REQUEST_CODE_CREATE_KEY); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_CREATE_KEY: { + if (resultCode == Activity.RESULT_OK) { + // select newly created key + long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), data.getData()); + mSelectKeyFragment.selectKey(masterKeyId); + } + break; + } + + default: + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + + /** + * callback from select secret key fragment + */ + @Override + public void onKeySelected(long secretKeyId) { + mAccSettings.setKeyId(secretKeyId); + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java new file mode 100644 index 000000000..cfc9c92ad --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.util.Log; + +public class AccountsListFragment extends ListFragment implements + LoaderManager.LoaderCallbacks { + + private static final String ARG_DATA_URI = "uri"; + + // This is the Adapter being used to display the list's data. + AccountsAdapter mAdapter; + + private Uri mDataUri; + + /** + * Creates new instance of this fragment + */ + public static AccountsListFragment newInstance(Uri dataUri) { + AccountsListFragment frag = new AccountsListFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View layout = super.onCreateView(inflater, container, + savedInstanceState); + ListView lv = (ListView) 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; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mDataUri = getArguments().getParcelable(ARG_DATA_URI); + + getListView().setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + String selectedAccountName = mAdapter.getItemAccountName(position); + Uri accountUri = mDataUri.buildUpon().appendEncodedPath(selectedAccountName).build(); + Log.d(Constants.TAG, "accountUri: " + accountUri); + + // edit account settings + Intent intent = new Intent(getActivity(), AccountSettingsActivity.class); + intent.setData(accountUri); + startActivity(intent); + } + }); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText(getString(R.string.api_settings_accounts_empty)); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + + // Create an empty adapter we will use to display the loaded data. + mAdapter = new AccountsAdapter(getActivity(), null, 0); + setListAdapter(mAdapter); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + // These are the Contacts rows that we will retrieve. + static final String[] PROJECTION = new String[]{ + KeychainContract.ApiAccounts._ID, // 0 + KeychainContract.ApiAccounts.ACCOUNT_NAME // 1 + }; + + public Loader onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null, + KeychainContract.ApiAccounts.ACCOUNT_NAME + " COLLATE LOCALIZED ASC"); + } + + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + } + + public void onLoaderReset(Loader 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); + } + + private class AccountsAdapter extends CursorAdapter { + private LayoutInflater mInflater; + + public AccountsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + } + + /** + * Similar to CursorAdapter.getItemId(). + * Required to build Uris for api app view, which is not based on row ids + * + * @param position + * @return + */ + public String getItemAccountName(int position) { + if (mDataValid && mCursor != null) { + if (mCursor.moveToPosition(position)) { + return mCursor.getString(1); + } else { + return null; + } + } else { + return null; + } + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView text = (TextView) view.findViewById(R.id.api_accounts_adapter_item_name); + + String accountName = cursor.getString(1); + text.setText(accountName); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.api_accounts_adapter_list_item, null); + } + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java new file mode 100644 index 000000000..f6f9631cb --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; +import android.view.Menu; +import android.view.MenuItem; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.util.Log; + +public class AppSettingsActivity extends ActionBarActivity { + private Uri mAppUri; + + private AppSettingsFragment mSettingsFragment; + private AccountsListFragment mAccountsListFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // let the actionbar look like Android's contact app + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setIcon(android.R.color.transparent); + actionBar.setHomeButtonEnabled(true); + + setContentView(R.layout.api_app_settings_activity); + + mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); + + Intent intent = getIntent(); + mAppUri = intent.getData(); + if (mAppUri == null) { + Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!"); + finish(); + return; + } else { + Log.d(Constants.TAG, "uri: " + mAppUri); + loadData(savedInstanceState, mAppUri); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.api_app_settings, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_api_settings_revoke: + revokeAccess(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void loadData(Bundle savedInstanceState, Uri appUri) { + // TODO: load this also like other fragment with newInstance arguments? + AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri); + mSettingsFragment.setAppSettings(settings); + + String appName; + PackageManager pm = getPackageManager(); + try { + ApplicationInfo ai = pm.getApplicationInfo(settings.getPackageName(), 0); + appName = (String) pm.getApplicationLabel(ai); + } catch (PackageManager.NameNotFoundException e) { + // fallback + appName = settings.getPackageName(); + } + setTitle(appName); + + Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build(); + Log.d(Constants.TAG, "accountsUri: " + accountsUri); + startListFragment(savedInstanceState, accountsUri); + } + + private void startListFragment(Bundle savedInstanceState, Uri dataUri) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Create an instance of the fragment + mAccountsListFragment = AccountsListFragment.newInstance(dataUri); + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.api_accounts_list_fragment, mAccountsListFragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + + private void revokeAccess() { + if (getContentResolver().delete(mAppUri, null, null) <= 0) { + throw new RuntimeException(); + } + finish(); + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java new file mode 100644 index 000000000..a6db02708 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * 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 . + */ + +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.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.util.Log; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class AppSettingsFragment extends Fragment { + + // model + private AppSettings mAppSettings; + + // view + private TextView mAppNameView; + private ImageView mAppIconView; + private TextView mPackageName; + private TextView mPackageSignature; + + 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 = (TextView) view.findViewById(R.id.api_app_settings_app_name); + mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon); + mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name); + mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature); + 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.getPackageSignature()); + byte[] digest = md.digest(); + String signature = new String(Hex.encode(digest)); + + mPackageSignature.setText(signature); + } catch (NoSuchAlgorithmException e) { + Log.e(Constants.TAG, "Should not happen!", e); + } + } + + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java similarity index 90% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java index f6f216efd..f86d279f0 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote.ui; import android.os.Bundle; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.DrawerActivity; -public class RegisteredAppsListActivity extends DrawerActivity { +public class AppsListActivity extends DrawerActivity { @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java similarity index 56% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java index 25d0c7593..22082e913 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -15,10 +15,12 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote.ui; -import android.content.ContentUris; +import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -26,14 +28,20 @@ import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.ImageView; +import android.widget.TextView; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; -public class RegisteredAppsListFragment extends ListFragment implements +public class AppsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks { // This is the Adapter being used to display the list's data. @@ -46,9 +54,10 @@ public class RegisteredAppsListFragment extends ListFragment implements getListView().setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView adapterView, View view, int position, long id) { + String selectedPackageName = mAdapter.getItemPackageName(position); // edit app settings Intent intent = new Intent(getActivity(), AppSettingsActivity.class); - intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id)); + intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName)); startActivity(intent); } }); @@ -70,7 +79,10 @@ public class RegisteredAppsListFragment extends ListFragment implements } // These are the Contacts rows that we will retrieve. - static final String[] PROJECTION = new String[]{ApiApps._ID, ApiApps.PACKAGE_NAME}; + static final String[] PROJECTION = new String[]{ + ApiApps._ID, // 0 + ApiApps.PACKAGE_NAME // 1 + }; public Loader onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This @@ -98,4 +110,65 @@ public class RegisteredAppsListFragment extends ListFragment implements mAdapter.swapCursor(null); } + private class RegisteredAppsAdapter extends CursorAdapter { + + private LayoutInflater mInflater; + private PackageManager mPM; + + public RegisteredAppsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + mPM = context.getApplicationContext().getPackageManager(); + } + + /** + * Similar to CursorAdapter.getItemId(). + * Required to build Uris for api app view, which is not based on row ids + * + * @param position + * @return + */ + public String getItemPackageName(int position) { + if (mDataValid && mCursor != null) { + if (mCursor.moveToPosition(position)) { + return mCursor.getString(1); + } else { + return null; + } + } else { + return null; + } + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name); + ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon); + + String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME)); + if (packageName != null) { + // get application name + try { + ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0); + + text.setText(mPM.getApplicationLabel(ai)); + icon.setImageDrawable(mPM.getApplicationIcon(ai)); + } catch (final PackageManager.NameNotFoundException e) { + // fallback + text.setText(packageName); + } + } else { + // fallback + text.setText(packageName); + } + + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.api_apps_adapter_list_item, null); + } + } + } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java similarity index 71% rename from OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java rename to OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index e20114853..307c9c61a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.service.remote; +package org.sufficientlysecure.keychain.remote.ui; import android.content.Intent; import android.os.Bundle; @@ -24,6 +24,7 @@ import android.os.Message; import android.os.Messenger; import android.support.v7.app.ActionBarActivity; import android.view.View; + import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.keychain.Constants; @@ -31,7 +32,10 @@ import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -41,6 +45,8 @@ import java.util.ArrayList; public class RemoteServiceActivity extends ActionBarActivity { public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; + public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX + + "API_ACTIVITY_CREATE_ACCOUNT"; public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX + "API_ACTIVITY_CACHE_PASSPHRASE"; public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX @@ -57,6 +63,8 @@ public class RemoteServiceActivity extends ActionBarActivity { // register action public static final String EXTRA_PACKAGE_NAME = "package_name"; public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature"; + // create acc action + public static final String EXTRA_ACC_NAME = "acc_name"; // select pub keys action public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; @@ -65,7 +73,9 @@ public class RemoteServiceActivity extends ActionBarActivity { public static final String EXTRA_ERROR_MESSAGE = "error_message"; // register view - private AppSettingsFragment mSettingsFragment; + private AppSettingsFragment mAppSettingsFragment; + // create acc view + private AccountSettingsFragment mAccSettingsFragment; // select pub keys view private SelectPublicKeyFragment mSelectFragment; @@ -85,6 +95,7 @@ public class RemoteServiceActivity extends ActionBarActivity { if (ACTION_REGISTER.equals(action)) { final String packageName = extras.getString(EXTRA_PACKAGE_NAME); final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); + Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName); // Inflate a "Done"/"Cancel" custom action bar view ActionBarHelper.setTwoButtonView(getSupportActionBar(), @@ -94,13 +105,52 @@ public class RemoteServiceActivity extends ActionBarActivity { public void onClick(View v) { // Allow + ProviderHelper.insertApiApp(RemoteServiceActivity.this, + mAppSettingsFragment.getAppSettings()); + + // give data through for new service call + Intent resultData = extras.getParcelable(EXTRA_DATA); + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + RemoteServiceActivity.this.finish(); + } + }, R.string.api_register_disallow, R.drawable.ic_action_cancel, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Disallow + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } + } + ); + + setContentView(R.layout.api_remote_register_app); + + mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); + + AppSettings settings = new AppSettings(packageName, packageSignature); + mAppSettingsFragment.setAppSettings(settings); + } else if (ACTION_CREATE_ACCOUNT.equals(action)) { + final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final String accName = extras.getString(EXTRA_ACC_NAME); + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setTwoButtonView(getSupportActionBar(), + R.string.api_settings_save, R.drawable.ic_action_done, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Save + // user needs to select a key! - if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) { - mSettingsFragment.setErrorOnSelectKeyFragment( + if (mAccSettingsFragment.getAccSettings().getKeyId() == Id.key.none) { + mAccSettingsFragment.setErrorOnSelectKeyFragment( getString(R.string.api_register_error_select_key)); } else { - ProviderHelper.insertApiApp(RemoteServiceActivity.this, - mSettingsFragment.getAppSettings()); + ProviderHelper.insertApiAccount(RemoteServiceActivity.this, + KeychainContract.ApiAccounts.buildBaseUri(packageName), + mAccSettingsFragment.getAccSettings()); // give data through for new service call Intent resultData = extras.getParcelable(EXTRA_DATA); @@ -108,29 +158,43 @@ public class RemoteServiceActivity extends ActionBarActivity { RemoteServiceActivity.this.finish(); } } - }, R.string.api_register_disallow, R.drawable.ic_action_cancel, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // Disallow - RemoteServiceActivity.this.setResult(RESULT_CANCELED); - RemoteServiceActivity.this.finish(); - } + }, R.string.api_settings_cancel, R.drawable.ic_action_cancel, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Cancel + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } } ); - setContentView(R.layout.api_app_register_activity); + setContentView(R.layout.api_remote_create_account); - mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_app_settings_fragment); + mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_account_settings_fragment); - AppSettings settings = new AppSettings(packageName, packageSignature); - mSettingsFragment.setAppSettings(settings); + AccountSettings settings = new AccountSettings(accName); + mAccSettingsFragment.setAccSettings(settings); } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); - Intent resultData = extras.getParcelable(EXTRA_DATA); + final Intent resultData = extras.getParcelable(EXTRA_DATA); + + PassphraseDialogFragment.show(this, secretKeyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + // return given params again, for calling the service method again + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + } else { + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + } + + RemoteServiceActivity.this.finish(); + } + }); - showPassphraseDialog(resultData, secretKeyId); } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); ArrayList missingUserIds = intent @@ -185,7 +249,7 @@ public class RemoteServiceActivity extends ActionBarActivity { } ); - setContentView(R.layout.api_app_select_pub_keys_activity); + setContentView(R.layout.api_remote_select_pub_keys); // set text on view HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); @@ -227,7 +291,7 @@ public class RemoteServiceActivity extends ActionBarActivity { } }); - setContentView(R.layout.api_app_error_message); + setContentView(R.layout.api_remote_error_message); // set text on view HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index daaff5d54..1c6aa7971 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -18,30 +18,61 @@ package org.sufficientlysecure.keychain.service; import android.app.IntentService; -import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import org.spongycastle.openpgp.*; + +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPUtil; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.pgp.*; +import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.pgp.PgpImportExport; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; -import org.sufficientlysecure.keychain.util.*; +import org.sufficientlysecure.keychain.util.HkpKeyServer; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.KeychainServiceListener; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; +import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +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 java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; -import java.util.GregorianCalendar; import java.util.List; /** @@ -84,35 +115,26 @@ public class KeychainIntentService extends IntentService // possible targets: public static final int TARGET_BYTES = 1; public static final int TARGET_URI = 2; - public static final int TARGET_STREAM = 3; // encrypt - public static final String ENCRYPT_SECRET_KEY_ID = "secret_key_id"; + public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id"; public static final String ENCRYPT_USE_ASCII_ARMOR = "use_ascii_armor"; public static final String ENCRYPT_ENCRYPTION_KEYS_IDS = "encryption_keys_ids"; public static final String ENCRYPT_COMPRESSION_ID = "compression_id"; - public static final String ENCRYPT_GENERATE_SIGNATURE = "generate_signature"; - public static final String ENCRYPT_SIGN_ONLY = "sign_only"; public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes"; public static final String ENCRYPT_INPUT_FILE = "input_file"; public static final String ENCRYPT_OUTPUT_FILE = "output_file"; - public static final String ENCRYPT_PROVIDER_URI = "provider_uri"; + public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase"; // decrypt/verify - public static final String DECRYPT_RETURN_BYTES = "return_binary"; public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes"; - public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric"; + public static final String DECRYPT_PASSPHRASE = "passphrase"; // save keyring - public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase"; - public static final String SAVE_KEYRING_CURRENT_PASSPHRASE = "current_passphrase"; - public static final String SAVE_KEYRING_USER_IDS = "user_ids"; - public static final String SAVE_KEYRING_KEYS = "keys"; - public static final String SAVE_KEYRING_KEYS_USAGES = "keys_usages"; - public static final String SAVE_KEYRING_KEYS_EXPIRY_DATES = "keys_expiry_dates"; - public static final String SAVE_KEYRING_MASTER_KEY_ID = "master_key_id"; + public static final String SAVE_KEYRING_PARCEL = "save_parcel"; public static final String SAVE_KEYRING_CAN_SIGN = "can_sign"; + // generate key public static final String GENERATE_KEY_ALGORITHM = "algorithm"; public static final String GENERATE_KEY_KEY_SIZE = "key_size"; @@ -128,10 +150,9 @@ public class KeychainIntentService extends IntentService // export key public static final String EXPORT_OUTPUT_STREAM = "export_output_stream"; public static final String EXPORT_FILENAME = "export_filename"; - public static final String EXPORT_KEY_TYPE = "export_key_type"; + public static final String EXPORT_SECRET = "export_secret"; public static final String EXPORT_ALL = "export_all"; public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; - public static final String EXPORT_KEY_RING_ROW_ID = "export_key_rind_row_id"; // upload key public static final String UPLOAD_KEY_SERVER = "upload_key_server"; @@ -150,17 +171,12 @@ public class KeychainIntentService extends IntentService */ // keys public static final String RESULT_NEW_KEY = "new_key"; - public static final String RESULT_NEW_KEY2 = "new_key2"; + public static final String RESULT_KEY_USAGES = "new_key_usages"; // encrypt - public static final String RESULT_SIGNATURE_BYTES = "signature_data"; - public static final String RESULT_SIGNATURE_STRING = "signature_text"; - public static final String RESULT_ENCRYPTED_STRING = "encrypted_message"; - public static final String RESULT_ENCRYPTED_BYTES = "encrypted_data"; - public static final String RESULT_URI = "result_uri"; + public static final String RESULT_BYTES = "encrypted_data"; // decrypt/verify - public static final String RESULT_DECRYPTED_STRING = "decrypted_message"; public static final String RESULT_DECRYPTED_BYTES = "decrypted_data"; public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature"; @@ -172,10 +188,6 @@ public class KeychainIntentService extends IntentService // export public static final String RESULT_EXPORT = "exported"; - // query - public static final String RESULT_QUERY_KEY_DATA = "query_key_data"; - public static final String RESULT_QUERY_KEY_SEARCH_RESULT = "query_key_search_result"; - Messenger mMessenger; private boolean mIsCanceled; @@ -225,20 +237,17 @@ public class KeychainIntentService extends IntentService /* Input */ int target = data.getInt(TARGET); - long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID); - String encryptionPassphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE); + long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID); + String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR); long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS); int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID); - boolean generateSignature = data.getBoolean(ENCRYPT_GENERATE_SIGNATURE); - boolean signOnly = data.getBoolean(ENCRYPT_SIGN_ONLY); - - InputStream inStream = null; - long inLength = -1; - InputData inputData = null; - OutputStream outStream = null; - String streamFilename = null; + InputStream inStream; + long inLength; + InputData inputData; + OutputStream outStream; +// String streamFilename = null; switch (target) { case TARGET_BYTES: /* encrypting bytes directly */ byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES); @@ -270,29 +279,30 @@ public class KeychainIntentService extends IntentService break; - case TARGET_STREAM: /* Encrypting stream from content uri */ - Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); - - // InputStream - InputStream in = getContentResolver().openInputStream(providerUri); - inLength = PgpHelper.getLengthOfStream(in); - inputData = new InputData(in, inLength); - - // OutputStream - try { - while (true) { - streamFilename = PgpHelper.generateRandomFilename(32); - if (streamFilename == null) { - throw new PgpGeneralException("couldn't generate random file name"); - } - openFileInput(streamFilename).close(); - } - } catch (FileNotFoundException e) { - // found a name that isn't used yet - } - outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); - - break; + // TODO: not used currently +// case TARGET_STREAM: /* Encrypting stream from content uri */ +// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); +// +// // InputStream +// InputStream in = getContentResolver().openInputStream(providerUri); +// inLength = PgpHelper.getLengthOfStream(in); +// inputData = new InputData(in, inLength); +// +// // OutputStream +// try { +// while (true) { +// streamFilename = PgpHelper.generateRandomFilename(32); +// if (streamFilename == null) { +// throw new PgpGeneralException("couldn't generate random file name"); +// } +// openFileInput(streamFilename).close(); +// } +// } catch (FileNotFoundException e) { +// // found a name that isn't used yet +// } +// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); +// +// break; default: throw new PgpGeneralException("No target choosen!"); @@ -304,45 +314,20 @@ public class KeychainIntentService extends IntentService new PgpSignEncrypt.Builder(this, inputData, outStream); builder.progress(this); - if (generateSignature) { - Log.d(Constants.TAG, "generating signature..."); - builder.enableAsciiArmorOutput(useAsciiArmor) - .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) - .signatureKeyId(secretKeyId) - .signatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .signaturePassphrase( - PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); + builder.enableAsciiArmorOutput(useAsciiArmor) + .compressionId(compressionId) + .symmetricEncryptionAlgorithm( + Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) + .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) + .encryptionKeyIds(encryptionKeyIds) + .symmetricPassphrase(symmetricPassphrase) + .signatureKeyId(signatureKeyId) + .signatureHashAlgorithm( + Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .signaturePassphrase( + PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); - builder.build().generateSignature(); - } else if (signOnly) { - Log.d(Constants.TAG, "sign only..."); - builder.enableAsciiArmorOutput(useAsciiArmor) - .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) - .signatureKeyId(secretKeyId) - .signatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .signaturePassphrase( - PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); - - builder.build().execute(); - } else { - Log.d(Constants.TAG, "encrypt..."); - builder.enableAsciiArmorOutput(useAsciiArmor) - .compressionId(compressionId) - .symmetricEncryptionAlgorithm( - Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) - .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) - .encryptionKeyIds(encryptionKeyIds) - .encryptionPassphrase(encryptionPassphrase) - .signatureKeyId(secretKeyId) - .signatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .signaturePassphrase( - PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); - - builder.build().execute(); - } + builder.build().execute(); outStream.close(); @@ -352,33 +337,20 @@ public class KeychainIntentService extends IntentService switch (target) { case TARGET_BYTES: - if (useAsciiArmor) { - String output = new String( - ((ByteArrayOutputStream) outStream).toByteArray()); - if (generateSignature) { - resultData.putString(RESULT_SIGNATURE_STRING, output); - } else { - resultData.putString(RESULT_ENCRYPTED_STRING, output); - } - } else { - byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); - if (generateSignature) { - resultData.putByteArray(RESULT_SIGNATURE_BYTES, output); - } else { - resultData.putByteArray(RESULT_ENCRYPTED_BYTES, output); - } - } + byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); + + resultData.putByteArray(RESULT_BYTES, output); break; case TARGET_URI: // nothing, file was written, just send okay break; - case TARGET_STREAM: - String uri = DataStream.buildDataStreamUri(streamFilename).toString(); - resultData.putString(RESULT_URI, uri); - - break; +// case TARGET_STREAM: +// String uri = DataStream.buildDataStreamUri(streamFilename).toString(); +// resultData.putString(RESULT_URI, uri); +// +// break; } OtherHelper.logDebugBundle(resultData, "resultData"); @@ -392,15 +364,13 @@ public class KeychainIntentService extends IntentService /* Input */ int target = data.getInt(TARGET); - long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID); byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES); - boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES); - boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC); + String passphrase = data.getString(DECRYPT_PASSPHRASE); - InputStream inStream = null; - long inLength = -1; - InputData inputData = null; - OutputStream outStream = null; + InputStream inStream; + long inLength; + InputData inputData; + OutputStream outStream; String streamFilename = null; switch (target) { case TARGET_BYTES: /* decrypting bytes directly */ @@ -435,29 +405,30 @@ public class KeychainIntentService extends IntentService break; - case TARGET_STREAM: /* decrypting stream from content uri */ - Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); - - // InputStream - InputStream in = getContentResolver().openInputStream(providerUri); - inLength = PgpHelper.getLengthOfStream(in); - inputData = new InputData(in, inLength); - - // OutputStream - try { - while (true) { - streamFilename = PgpHelper.generateRandomFilename(32); - if (streamFilename == null) { - throw new PgpGeneralException("couldn't generate random file name"); - } - openFileInput(streamFilename).close(); - } - } catch (FileNotFoundException e) { - // found a name that isn't used yet - } - outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); - - break; + // TODO: not used, maybe contains code useful for new decrypt method for files? +// case TARGET_STREAM: /* decrypting stream from content uri */ +// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); +// +// // InputStream +// InputStream in = getContentResolver().openInputStream(providerUri); +// inLength = PgpHelper.getLengthOfStream(in); +// inputData = new InputData(in, inLength); +// +// // OutputStream +// try { +// while (true) { +// streamFilename = PgpHelper.generateRandomFilename(32); +// if (streamFilename == null) { +// throw new PgpGeneralException("couldn't generate random file name"); +// } +// openFileInput(streamFilename).close(); +// } +// } catch (FileNotFoundException e) { +// // found a name that isn't used yet +// } +// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); +// +// break; default: throw new PgpGeneralException("No target choosen!"); @@ -473,8 +444,8 @@ public class KeychainIntentService extends IntentService PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream); builder.progressDialogUpdater(this); - builder.assumeSymmetric(assumeSymmetricEncryption) - .passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); + builder.allowSymmetricDecryption(true) + .passphrase(passphrase); PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute(); @@ -486,25 +457,18 @@ public class KeychainIntentService extends IntentService switch (target) { case TARGET_BYTES: - if (returnBytes) { - byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); - resultData.putByteArray(RESULT_DECRYPTED_BYTES, output); - } else { - String output = new String( - ((ByteArrayOutputStream) outStream).toByteArray()); - resultData.putString(RESULT_DECRYPTED_STRING, output); - } - + byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); + resultData.putByteArray(RESULT_DECRYPTED_BYTES, output); break; case TARGET_URI: // nothing, file was written, just send okay and verification bundle break; - case TARGET_STREAM: - String uri = DataStream.buildDataStreamUri(streamFilename).toString(); - resultData.putString(RESULT_URI, uri); - - break; +// case TARGET_STREAM: +// String uri = DataStream.buildDataStreamUri(streamFilename).toString(); +// resultData.putString(RESULT_URI, uri); +// +// break; } OtherHelper.logDebugBundle(resultData, "resultData"); @@ -516,38 +480,42 @@ public class KeychainIntentService extends IntentService } else if (ACTION_SAVE_KEYRING.equals(action)) { try { /* Input */ - String oldPassPhrase = data.getString(SAVE_KEYRING_CURRENT_PASSPHRASE); - String newPassPhrase = data.getString(SAVE_KEYRING_NEW_PASSPHRASE); + SaveKeyringParcel saveParams = data.getParcelable(SAVE_KEYRING_PARCEL); + String oldPassphrase = saveParams.oldPassphrase; + String newPassphrase = saveParams.newPassphrase; boolean canSign = true; if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) { canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN); } - if (newPassPhrase == null) { - newPassPhrase = oldPassPhrase; + if (newPassphrase == null) { + newPassphrase = oldPassphrase; } - ArrayList userIds = data.getStringArrayList(SAVE_KEYRING_USER_IDS); - ArrayList keys = PgpConversionHelper.BytesToPGPSecretKeyList(data - .getByteArray(SAVE_KEYRING_KEYS)); - ArrayList keysUsages = data.getIntegerArrayList(SAVE_KEYRING_KEYS_USAGES); - ArrayList keysExpiryDates = - (ArrayList) data.getSerializable(SAVE_KEYRING_KEYS_EXPIRY_DATES); - long masterKeyId = data.getLong(SAVE_KEYRING_MASTER_KEY_ID); + long masterKeyId = saveParams.keys.get(0).getKeyID(); - PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); /* Operation */ if (!canSign) { - keyOperations.changeSecretKeyPassphrase( - ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId), - oldPassPhrase, newPassPhrase); + PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 50, 100)); + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId); + keyRing = keyOperations.changeSecretKeyPassphrase(keyRing, + oldPassphrase, newPassphrase); + setProgress(R.string.progress_saving_key_ring, 50, 100); + ProviderHelper.saveKeyRing(this, keyRing); + setProgress(R.string.progress_done, 100, 100); } else { - PGPPublicKey pubkey = ProviderHelper.getPGPPublicKeyByKeyId(this, masterKeyId); - keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates, - pubkey, oldPassPhrase, newPassPhrase); + PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 90, 100)); + PGPSecretKeyRing privkey = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId); + PGPPublicKeyRing pubkey = ProviderHelper.getPGPPublicKeyRing(this, masterKeyId); + PgpKeyOperation.Pair pair = + keyOperations.buildSecretKey(privkey, pubkey, saveParams); + setProgress(R.string.progress_saving_key_ring, 90, 100); + // save the pair + ProviderHelper.saveKeyRing(this, pair.second, pair.first); + setProgress(R.string.progress_done, 100, 100); } - PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase); + PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassphrase); /* Output */ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); @@ -563,7 +531,7 @@ public class KeychainIntentService extends IntentService boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY); /* Operation */ - PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); + PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100)); PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize, passphrase, masterKey); @@ -583,24 +551,37 @@ public class KeychainIntentService extends IntentService try { /* Input */ String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE); + ArrayList newKeys = new ArrayList(); + ArrayList keyUsageList = new ArrayList(); /* Operation */ - int keysTotal = 2; + int keysTotal = 3; int keysCreated = 0; setProgress( getApplicationContext().getResources(). getQuantityString(R.plurals.progress_generating, keysTotal), keysCreated, keysTotal); - PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); + PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100)); PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa, 4096, passphrase, true); + newKeys.add(masterKey); + keyUsageList.add(KeyFlags.CERTIFY_OTHER); keysCreated++; setProgress(keysCreated, keysTotal); PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa, 4096, passphrase, false); + newKeys.add(subKey); + keyUsageList.add(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE); + keysCreated++; + setProgress(keysCreated, keysTotal); + + subKey = keyOperations.createKey(Id.choice.algorithm.rsa, + 4096, passphrase, false); + newKeys.add(subKey); + keyUsageList.add(KeyFlags.SIGN_DATA); keysCreated++; setProgress(keysCreated, keysTotal); @@ -608,11 +589,11 @@ public class KeychainIntentService extends IntentService // for sign /* Output */ + Bundle resultData = new Bundle(); resultData.putByteArray(RESULT_NEW_KEY, - PgpConversionHelper.PGPSecretKeyToBytes(masterKey)); - resultData.putByteArray(RESULT_NEW_KEY2, - PgpConversionHelper.PGPSecretKeyToBytes(subKey)); + PgpConversionHelper.PGPSecretKeyArrayListToBytes(newKeys)); + resultData.putIntegerArrayList(RESULT_KEY_USAGES, keyUsageList); OtherHelper.logDebugBundle(resultData, "resultData"); @@ -657,53 +638,50 @@ public class KeychainIntentService extends IntentService } else if (ACTION_EXPORT_KEYRING.equals(action)) { try { - /* Input */ - int keyType = Id.type.public_key; - if (data.containsKey(EXPORT_KEY_TYPE)) { - keyType = data.getInt(EXPORT_KEY_TYPE); - } - + boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); + long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); String outputFile = data.getString(EXPORT_FILENAME); - long[] rowIds = new long[0]; - - // If not exporting all keys get the rowIds of the keys to export from the intent + // If not exporting all keys get the masterKeyIds of the keys to export from the intent boolean exportAll = data.getBoolean(EXPORT_ALL); - if (!exportAll) { - rowIds = data.getLongArray(EXPORT_KEY_RING_ROW_ID); - } - - /* Operation */ // check if storage is ready if (!FileHelper.isStorageMounted(outputFile)) { throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); } - // OutputStream - FileOutputStream outStream = new FileOutputStream(outputFile); + ArrayList publicMasterKeyIds = new ArrayList(); + ArrayList secretMasterKeyIds = new ArrayList(); - ArrayList keyRingRowIds = new ArrayList(); - if (exportAll) { - - // get all key ring row ids based on export type - if (keyType == Id.type.public_key) { - keyRingRowIds = ProviderHelper.getPublicKeyRingsRowIds(this); - } else { - keyRingRowIds = ProviderHelper.getSecretKeyRingsRowIds(this); - } - } else { - for (long rowId : rowIds) { - keyRingRowIds.add(rowId); + String selection = null; + if(!exportAll) { + selection = KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN( "; + for(long l : masterKeyIds) { + selection += Long.toString(l) + ","; } + selection = selection.substring(0, selection.length()-1) + " )"; } - Bundle resultData; + Cursor cursor = getContentResolver().query(KeyRings.buildUnifiedKeyRingsUri(), + new String[]{ KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET }, + selection, null, null); + try { + cursor.moveToFirst(); + do { + // export public either way + publicMasterKeyIds.add(cursor.getLong(0)); + // add secret if available (and requested) + if(exportSecret && cursor.getInt(1) != 0) + secretMasterKeyIds.add(cursor.getLong(0)); + } while(cursor.moveToNext()); + } finally { + cursor.close(); + } PgpImportExport pgpImportExport = new PgpImportExport(this, this, this); - - resultData = pgpImportExport - .exportKeyRings(keyRingRowIds, keyType, outStream); + Bundle resultData = pgpImportExport + .exportKeyRings(publicMasterKeyIds, secretMasterKeyIds, + new FileOutputStream(outputFile)); if (mIsCanceled) { boolean isDeleted = new File(outputFile).delete(); @@ -747,45 +725,54 @@ public class KeychainIntentService extends IntentService HkpKeyServer server = new HkpKeyServer(keyServer); for (ImportKeysListEntry entry : entries) { - byte[] downloadedKey = server.get(entry.getKeyId()).getBytes(); + // if available use complete fingerprint for get request + byte[] downloadedKeyBytes; + if (entry.getFingerPrintHex() != null) { + downloadedKeyBytes = server.get("0x" + entry.getFingerPrintHex()).getBytes(); + } else { + downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes(); + } - /** - * TODO: copied from ImportKeysListLoader - * - * - * this parses the downloaded key - */ - // need to have access to the bufferedInput, so we can reuse it for the possible - // PGPObject chunks after the first one, e.g. files with several consecutive ASCII - // armor blocks + // create PGPKeyRing object based on downloaded armored key + PGPKeyRing downloadedKey = null; BufferedInputStream bufferedInput = - new BufferedInputStream(new ByteArrayInputStream(downloadedKey)); - try { + new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes)); + if (bufferedInput.available() > 0) { + InputStream in = PGPUtil.getDecoderStream(bufferedInput); + PGPObjectFactory objectFactory = new PGPObjectFactory(in); - // read all available blocks... (asc files can contain many blocks with BEGIN END) - while (bufferedInput.available() > 0) { - InputStream in = PGPUtil.getDecoderStream(bufferedInput); - PGPObjectFactory objectFactory = new PGPObjectFactory(in); + // get first object in block + Object obj; + if ((obj = objectFactory.nextObject()) != null) { + Log.d(Constants.TAG, "Found class: " + obj.getClass()); - // go through all objects in this block - Object obj; - while ((obj = objectFactory.nextObject()) != null) { - Log.d(Constants.TAG, "Found class: " + obj.getClass()); - - if (obj instanceof PGPKeyRing) { - PGPKeyRing newKeyring = (PGPKeyRing) obj; - - entry.setBytes(newKeyring.getEncoded()); - } else { - Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!"); - } + if (obj instanceof PGPKeyRing) { + downloadedKey = (PGPKeyRing) obj; + } else { + throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); } } - } catch (Exception e) { - Log.e(Constants.TAG, "Exception on parsing key file!", e); } + + // verify downloaded key by comparing fingerprints + if (entry.getFingerPrintHex() != null) { + String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex( + downloadedKey.getPublicKey().getFingerprint()); + if (downloadedKeyFp.equals(entry.getFingerPrintHex())) { + Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " + + "the requested fingerprint!"); + } else { + throw new PgpGeneralException("fingerprint of downloaded key is " + + "NOT the same as the requested fingerprint!"); + } + } + + // save key bytes in entry object for doing the + // actual import afterwards + entry.setBytes(downloadedKey.getEncoded()); } + Intent importIntent = new Intent(this, KeychainIntentService.class); importIntent.setAction(ACTION_IMPORT_KEYRING); Bundle importData = new Bundle(); @@ -809,16 +796,24 @@ public class KeychainIntentService extends IntentService ArrayList userIds = data.getStringArrayList(CERTIFY_KEY_UIDS); /* Operation */ - String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this, + String signaturePassphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); + if (signaturePassphrase == null) { + throw new PgpGeneralException("Unable to obtain passphrase"); + } - PgpKeyOperation keyOperation = new PgpKeyOperation(this, this); - PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId, - userIds, signaturePassPhrase); + PgpKeyOperation keyOperation = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100)); + PGPPublicKeyRing publicRing = ProviderHelper.getPGPPublicKeyRing(this, pubKeyId); + PGPPublicKey publicKey = publicRing.getPublicKey(pubKeyId); + PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(this, + masterKeyId); + publicKey = keyOperation.certifyKey(certificationKey, publicKey, + userIds, signaturePassphrase); + publicRing = PGPPublicKeyRing.insertPublicKey(publicRing, publicKey); // store the signed key in our local cache PgpImportExport pgpImportExport = new PgpImportExport(this, null); - int retval = pgpImportExport.storeKeyRingInCache(signedPubKeyRing); + int retval = pgpImportExport.storeKeyRingInCache(publicRing); if (retval != Id.return_value.ok && retval != Id.return_value.updated) { throw new PgpGeneralException("Failed to store signed key in local cache"); } @@ -835,6 +830,10 @@ public class KeychainIntentService extends IntentService if (this.mIsCanceled) { return; } + // contextualize the exception, if necessary + if (e instanceof PgpGeneralMsgIdException) { + e = ((PgpGeneralMsgIdException) e).getContextualized(this); + } Log.e(Constants.TAG, "ApgService Exception: ", e); e.printStackTrace(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 176d09c1a..962b304c7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -24,19 +24,29 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.*; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.util.LongSparseArray; import android.util.Log; -import android.util.LongSparseArray; + import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import java.util.Date; @@ -86,7 +96,7 @@ public class PassphraseCacheService extends Service { Intent intent = new Intent(context, PassphraseCacheService.class); intent.setAction(ACTION_PASSPHRASE_CACHE_ADD); - intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl()); + intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassphraseCacheTtl()); intent.putExtra(EXTRA_PASSPHRASE, passphrase); intent.putExtra(EXTRA_KEY_ID, keyId); @@ -161,15 +171,11 @@ public class PassphraseCacheService extends Service { // try to get master key id which is used as an identifier for cached passphrases long masterKeyId = keyId; if (masterKeyId != Id.key.symmetric) { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(this, keyId); - if (keyRing == null) { + masterKeyId = ProviderHelper.getMasterKeyId(this, + KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId))); + // Failure + if(masterKeyId == 0) return null; - } - PGPSecretKey masterKey = PgpKeyHelper.getMasterKey(keyRing); - if (masterKey == null) { - return null; - } - masterKeyId = masterKey.getKeyID(); } Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId); @@ -202,8 +208,7 @@ public class PassphraseCacheService extends Service { public static boolean hasPassphrase(Context context, long secretKeyId) { // check if the key has no passphrase try { - PGPSecretKeyRing secRing = ProviderHelper - .getPGPSecretKeyRingByKeyId(context, secretKeyId); + PGPSecretKeyRing secRing = ProviderHelper.getPGPSecretKeyRing(context, secretKeyId); PGPSecretKey secretKey = null; boolean foundValidKey = false; for (Iterator keys = secRing.getSecretKeys(); keys.hasNext(); ) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java new file mode 100644 index 000000000..7c2dcf2c1 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 Ash Hughes + * + * 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 . + */ + + +package org.sufficientlysecure.keychain.service; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.spongycastle.openpgp.PGPSecretKey; +import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; + +import java.util.ArrayList; +import java.util.GregorianCalendar; + +public class SaveKeyringParcel implements Parcelable { + + public ArrayList userIDs; + public ArrayList originalIDs; + public ArrayList deletedIDs; + public boolean[] newIDs; + public boolean primaryIDChanged; + public boolean[] moddedKeys; + public ArrayList deletedKeys; + public ArrayList keysExpiryDates; + public ArrayList keysUsages; + public String newPassphrase; + public String oldPassphrase; + public boolean[] newKeys; + public ArrayList keys; + public String originalPrimaryID; + + public SaveKeyringParcel() {} + + private SaveKeyringParcel(Parcel source) { + userIDs = (ArrayList) source.readSerializable(); + originalIDs = (ArrayList) source.readSerializable(); + deletedIDs = (ArrayList) source.readSerializable(); + newIDs = source.createBooleanArray(); + primaryIDChanged = source.readByte() != 0; + moddedKeys = source.createBooleanArray(); + byte[] tmp = source.createByteArray(); + if (tmp == null) { + deletedKeys = null; + } else { + deletedKeys = PgpConversionHelper.BytesToPGPSecretKeyList(tmp); + } + keysExpiryDates = (ArrayList) source.readSerializable(); + keysUsages = source.readArrayList(Integer.class.getClassLoader()); + newPassphrase = source.readString(); + oldPassphrase = source.readString(); + newKeys = source.createBooleanArray(); + keys = PgpConversionHelper.BytesToPGPSecretKeyList(source.createByteArray()); + originalPrimaryID = source.readString(); + } + + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeSerializable(userIDs); //might not be the best method to store. + destination.writeSerializable(originalIDs); + destination.writeSerializable(deletedIDs); + destination.writeBooleanArray(newIDs); + destination.writeByte((byte) (primaryIDChanged ? 1 : 0)); + destination.writeBooleanArray(moddedKeys); + byte[] tmp = null; + if (deletedKeys.size() != 0) { + tmp = PgpConversionHelper.PGPSecretKeyArrayListToBytes(deletedKeys); + } + destination.writeByteArray(tmp); + destination.writeSerializable(keysExpiryDates); + destination.writeList(keysUsages); + destination.writeString(newPassphrase); + destination.writeString(oldPassphrase); + destination.writeBooleanArray(newKeys); + destination.writeByteArray(PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys)); + destination.writeString(originalPrimaryID); + } + + public static final Creator CREATOR = new Creator() { + public SaveKeyringParcel createFromParcel(final Parcel source) { + return new SaveKeyringParcel(source); + } + + public SaveKeyringParcel[] newArray(final int size) { + return new SaveKeyringParcel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java deleted file mode 100644 index 837295018..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2013-2014 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.service.remote; - -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.View.OnClickListener; -import android.view.ViewGroup; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.widget.*; -import android.widget.AdapterView.OnItemSelectedListener; -import com.beardedhen.androidbootstrap.BootstrapButton; -import org.spongycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment; -import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter; -import org.sufficientlysecure.keychain.util.AlgorithmNames; -import org.sufficientlysecure.keychain.util.Log; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class AppSettingsFragment extends Fragment implements - SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { - - // model - private AppSettings mAppSettings; - - // view - private LinearLayout mAdvancedSettingsContainer; - private BootstrapButton mAdvancedSettingsButton; - private TextView mAppNameView; - private ImageView mAppIconView; - private Spinner mEncryptionAlgorithm; - private Spinner mHashAlgorithm; - private Spinner mCompression; - private TextView mPackageName; - private TextView mPackageSignature; - - private SelectSecretKeyLayoutFragment mSelectKeyFragment; - - KeyValueSpinnerAdapter mEncryptionAdapter; - KeyValueSpinnerAdapter mHashAdapter; - KeyValueSpinnerAdapter mCompressionAdapter; - - public AppSettings getAppSettings() { - return mAppSettings; - } - - public void setAppSettings(AppSettings appSettings) { - this.mAppSettings = appSettings; - setPackage(appSettings.getPackageName()); - mPackageName.setText(appSettings.getPackageName()); - - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(appSettings.getPackageSignature()); - byte[] digest = md.digest(); - String signature = new String(Hex.encode(digest)); - - mPackageSignature.setText(signature); - } catch (NoSuchAlgorithmException e) { - Log.e(Constants.TAG, "Should not happen!", e); - } - - mSelectKeyFragment.selectKey(appSettings.getKeyId()); - mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(appSettings - .getEncryptionAlgorithm())); - mHashAlgorithm.setSelection(mHashAdapter.getPosition(appSettings.getHashAlgorithm())); - mCompression.setSelection(mCompressionAdapter.getPosition(appSettings.getCompression())); - } - - /** - * 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); - initView(view); - return view; - } - - /** - * Set error String on key selection - * - * @param error - */ - public void setErrorOnSelectKeyFragment(String error) { - mSelectKeyFragment.setError(error); - } - - private void initView(View view) { - mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById( - R.id.api_app_settings_select_key_fragment); - mSelectKeyFragment.setCallback(this); - - mAdvancedSettingsButton = (BootstrapButton) view - .findViewById(R.id.api_app_settings_advanced_button); - mAdvancedSettingsContainer = (LinearLayout) view - .findViewById(R.id.api_app_settings_advanced); - - mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name); - mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon); - mEncryptionAlgorithm = (Spinner) view - .findViewById(R.id.api_app_settings_encryption_algorithm); - mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm); - mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression); - mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name); - mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature); - - AlgorithmNames algorithmNames = new AlgorithmNames(getActivity()); - - mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(), - algorithmNames.getEncryptionNames()); - mEncryptionAlgorithm.setAdapter(mEncryptionAdapter); - mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - mAppSettings.setEncryptionAlgorithm((int) id); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - - mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames()); - mHashAlgorithm.setAdapter(mHashAdapter); - mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - mAppSettings.setHashAlgorithm((int) id); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - - mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(), - algorithmNames.getCompressionNames()); - mCompression.setAdapter(mCompressionAdapter); - mCompression.setOnItemSelectedListener(new OnItemSelectedListener() { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - mAppSettings.setCompression((int) id); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - - final Animation visibleAnimation = new AlphaAnimation(0.0f, 1.0f); - visibleAnimation.setDuration(250); - final Animation invisibleAnimation = new AlphaAnimation(1.0f, 0.0f); - invisibleAnimation.setDuration(250); - - // TODO: Better: collapse/expand animation - // final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, - // Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f, - // Animation.RELATIVE_TO_SELF, 0.0f);u - // animation2.setDuration(150); - - mAdvancedSettingsButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) { - mAdvancedSettingsContainer.startAnimation(invisibleAnimation); - mAdvancedSettingsContainer.setVisibility(View.GONE); - mAdvancedSettingsButton.setText(getString(R.string.api_settings_show_advanced)); - mAdvancedSettingsButton.setLeftIcon("fa-caret-up"); - } else { - mAdvancedSettingsContainer.startAnimation(visibleAnimation); - mAdvancedSettingsContainer.setVisibility(View.VISIBLE); - mAdvancedSettingsButton.setText(getString(R.string.api_settings_hide_advanced)); - mAdvancedSettingsButton.setLeftIcon("fa-caret-down"); - } - } - }); - } - - private void setPackage(String packageName) { - PackageManager pm = getActivity().getApplicationContext().getPackageManager(); - - // get application name and icon from package manager - String appName = null; - Drawable appIcon = null; - try { - ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); - - appName = (String) pm.getApplicationLabel(ai); - appIcon = pm.getApplicationIcon(ai); - } catch (final NameNotFoundException e) { - // fallback - appName = packageName; - } - mAppNameView.setText(appName); - mAppIconView.setImageDrawable(appIcon); - } - - /** - * callback from select secret key fragment - */ - @Override - public void onKeySelected(long secretKeyId) { - mAppSettings.setKeyId(secretKeyId); - } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsAdapter.java deleted file mode 100644 index e0dc4162f..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RegisteredAppsAdapter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann - * - * 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 . - */ - -package org.sufficientlysecure.keychain.service.remote; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.database.Cursor; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; - -public class RegisteredAppsAdapter extends CursorAdapter { - - private LayoutInflater mInflater; - private PackageManager mPM; - - public RegisteredAppsAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - - mInflater = LayoutInflater.from(context); - mPM = context.getApplicationContext().getPackageManager(); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name); - ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon); - - String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME)); - if (packageName != null) { - // get application name - try { - ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0); - - text.setText(mPM.getApplicationLabel(ai)); - icon.setImageDrawable(mPM.getApplicationIcon(ai)); - } catch (final NameNotFoundException e) { - // fallback - text.setText(packageName); - } - } else { - // fallback - text.setText(packageName); - } - - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.api_apps_adapter_list_item, null); - } - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index f2e6c4bd9..2efa8a69c 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2014 Dominik Schürmann * Copyright (C) 2011 Senecaso - * + * * 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 @@ -32,16 +32,20 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.View; import android.view.View.OnClickListener; -import android.widget.*; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; import com.beardedhen.androidbootstrap.BootstrapButton; import org.spongycastle.openpgp.PGPPublicKeyRing; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -140,8 +144,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements } Log.e(Constants.TAG, "uri: " + mDataUri); - PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri); - mUserIds = (ListView) findViewById(R.id.user_ids); mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true); @@ -150,20 +152,12 @@ public class CertifyKeyActivity extends ActionBarActivity implements getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this); getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); - if (signKey != null) { - mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID(); - } - if (mPubKeyId == 0) { - Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!"); - finish(); - return; - } } static final String[] KEYRING_PROJECTION = new String[] { KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.Keys.FINGERPRINT, KeychainContract.UserIds.USER_ID, }; @@ -184,11 +178,13 @@ public class CertifyKeyActivity extends ActionBarActivity implements @Override public Loader onCreateLoader(int id, Bundle args) { switch(id) { - case LOADER_ID_KEYRING: - return new CursorLoader(this, mDataUri, KEYRING_PROJECTION, null, null, null); + case LOADER_ID_KEYRING: { + Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(this, uri, KEYRING_PROJECTION, null, null, null); + } case LOADER_ID_USER_IDS: { - Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); - return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER); + Uri uri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); + return new CursorLoader(this, uri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER); } } return null; @@ -200,20 +196,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements case LOADER_ID_KEYRING: // the first key here is our master key if (data.moveToFirst()) { - long keyId = data.getLong(INDEX_MASTER_KEY_ID); - String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId); + // TODO: put findViewById in onCreate! + mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID); + String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(mPubKeyId); ((TextView) findViewById(R.id.key_id)).setText(keyIdStr); String mainUserId = data.getString(INDEX_USER_ID); ((TextView) findViewById(R.id.main_user_id)).setText(mainUserId); byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT); - if (fingerprintBlob == null) { - // FALLBACK for old database entries - fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri); - } - String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); - ((TextView) findViewById(R.id.fingerprint)).setText(OtherHelper.colorizeFingerprint(fingerprint)); + String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob); + ((TextView) findViewById(R.id.fingerprint)) + .setText(PgpKeyHelper.colorizeFingerprint(fingerprint)); } break; case LOADER_ID_USER_IDS: @@ -231,37 +225,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements } } - private void showPassphraseDialog(final long secretKeyId) { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - startSigning(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this, - messenger, secretKeyId); - - passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); - } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key!"); - // send message to handler to start certification directly - returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); - } - } - /** * handles the UI bits of the signing process on the UI thread */ private void initiateSigning() { - PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, mPubKeyId); + PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRing(this, mPubKeyId); if (pubring != null) { // if we have already signed this key, dont bother doing it again boolean alreadySigned = false; @@ -284,7 +252,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements */ String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId); if (passphrase == null) { - showPassphraseDialog(mMasterKeyId); + PassphraseDialogFragment.show(this, mMasterKeyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + startSigning(); + } + } + }); // bail out; need to wait until the user has entered the passphrase before trying again return; } else { @@ -307,7 +283,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements // Bail out if there is not at least one user id selected ArrayList userIds = mUserIdsAdapter.getSelectedUserIds(); - if(userIds.isEmpty()) { + if (userIds.isEmpty()) { Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!", Toast.LENGTH_SHORT).show(); return; @@ -327,11 +303,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after signing is done in ApgService + // Message is received after signing is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) { public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { @@ -380,11 +356,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after uploading is done in ApgService + // Message is received after uploading is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 3e389c034..8533e9072 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Dominik Schürmann + * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2010 Thialfihar * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,180 +17,48 @@ package org.sufficientlysecure.keychain.ui; -import android.annotation.SuppressLint; -import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.animation.AnimationUtils; -import android.widget.*; -import com.beardedhen.androidbootstrap.BootstrapButton; -import com.devspark.appmsg.AppMsg; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.spongycastle.openpgp.PGPPublicKeyRing; +import android.support.v4.view.PagerTabStrip; +import android.support.v4.view.ViewPager; +import android.widget.Toast; + import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; -import java.io.*; import java.util.regex.Matcher; -@SuppressLint("NewApi") public class DecryptActivity extends DrawerActivity { /* Intents */ - // without permission public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT"; /* EXTRA keys for input */ public static final String EXTRA_TEXT = "text"; - private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006; - private static final int RESULT_CODE_FILE = 0x00007003; + ViewPager mViewPager; + PagerTabStrip mPagerTabStrip; + PagerTabStripAdapter mTabsAdapter; - private long mSignatureKeyId = 0; + Bundle mMessageFragmentBundle = new Bundle(); + Bundle mFileFragmentBundle = new Bundle(); + int mSwitchToTab = PAGER_TAB_MESSAGE; - private boolean mReturnResult = false; - - // TODO: replace signed only checks with something more intelligent - // PgpDecryptVerify should handle all automatically!!! - private boolean mSignedOnly = false; - private boolean mAssumeSymmetricEncryption = false; - - private EditText mMessage = null; - private RelativeLayout mSignatureLayout = null; - private ImageView mSignatureStatusImage = null; - private TextView mUserId = null; - private TextView mUserIdRest = null; - - private ViewFlipper mSource = null; - private TextView mSourceLabel = null; - private ImageView mSourcePrevious = null; - private ImageView mSourceNext = null; - - private int mDecryptTarget; - - private EditText mFilename = null; - private CheckBox mDeleteAfter = null; - private BootstrapButton mBrowse = null; - private BootstrapButton mLookupKey = null; - - private String mInputFilename = null; - private String mOutputFilename = null; - - private Uri mContentUri = null; - private boolean mReturnBinary = false; - - private long mSecretKeyId = Id.key.none; - - private FileDialogFragment mFileDialog; - - private boolean mDecryptImmediately = false; - - private BootstrapButton mDecryptButton; + private static final int PAGER_TAB_MESSAGE = 0; + private static final int PAGER_TAB_FILE = 1; private void initView() { - mSource = (ViewFlipper) findViewById(R.id.source); - mSourceLabel = (TextView) findViewById(R.id.sourceLabel); - mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious); - mSourceNext = (ImageView) findViewById(R.id.sourceNext); + mViewPager = (ViewPager) findViewById(R.id.decrypt_pager); + mPagerTabStrip = (PagerTabStrip) findViewById(R.id.decrypt_pager_tab_strip); - mSourcePrevious.setClickable(true); - mSourcePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_right_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_right_out)); - mSource.showPrevious(); - updateSource(); - } - }); - - mSourceNext.setClickable(true); - OnClickListener nextSourceClickListener = new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_left_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_left_out)); - mSource.showNext(); - updateSource(); - } - }; - mSourceNext.setOnClickListener(nextSourceClickListener); - - mSourceLabel.setClickable(true); - mSourceLabel.setOnClickListener(nextSourceClickListener); - - mMessage = (EditText) findViewById(R.id.message); - mSignatureLayout = (RelativeLayout) findViewById(R.id.signature); - mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status); - mUserId = (TextView) findViewById(R.id.mainUserId); - mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); - - // measure the height of the source_file view and set the message view's min height to that, - // so it fills mSource fully... bit of a hack. - View tmp = findViewById(R.id.sourceFile); - tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - int height = tmp.getMeasuredHeight(); - mMessage.setMinimumHeight(height); - - mFilename = (EditText) findViewById(R.id.filename); - mBrowse = (BootstrapButton) findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); - } - }); - - mLookupKey = (BootstrapButton) findViewById(R.id.lookup_key); - mLookupKey.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - lookupUnknownKey(mSignatureKeyId); - } - }); - - mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption); - - // default: message source - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceMessage) { - mSource.showNext(); - } - - mDecryptButton = (BootstrapButton) findViewById(R.id.action_decrypt); - mDecryptButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - decryptClicked(); - } - }); + mTabsAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(mTabsAdapter); } @Override @@ -206,68 +74,17 @@ public class DecryptActivity extends DrawerActivity { setupDrawerNavigation(savedInstanceState); - // Handle intent actions + // Handle intent actions, maybe changes the bundles handleActions(getIntent()); - if (mSource.getCurrentView().getId() == R.id.sourceMessage - && mMessage.getText().length() == 0) { - - CharSequence clipboardText = ClipboardReflection.getClipboardText(this); - - String data = ""; - if (clipboardText != null) { - Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText); - if (!matcher.matches()) { - matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(clipboardText); - } - if (matcher.matches()) { - data = matcher.group(1); - mMessage.setText(data); - AppMsg.makeText(this, R.string.using_clipboard_content, AppMsg.STYLE_INFO) - .show(); - } - } - } - - mSignatureLayout.setVisibility(View.GONE); - mSignatureLayout.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - if (mSignatureKeyId == 0) { - return; - } - PGPPublicKeyRing key = ProviderHelper.getPGPPublicKeyRingByKeyId( - DecryptActivity.this, mSignatureKeyId); - if (key != null) { - Intent intent = new Intent(DecryptActivity.this, ImportKeysActivity.class); - intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); - intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, mSignatureKeyId); - startActivity(intent); - } - } - }); - - if (mReturnResult) { - mSourcePrevious.setClickable(false); - mSourcePrevious.setEnabled(false); - mSourcePrevious.setVisibility(View.INVISIBLE); - - mSourceNext.setClickable(false); - mSourceNext.setEnabled(false); - mSourceNext.setVisibility(View.INVISIBLE); - - mSourceLabel.setClickable(false); - mSourceLabel.setEnabled(false); - } - - updateSource(); - - if (mDecryptImmediately - || (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText() - .length() > 0 || mContentUri != null))) { - decryptClicked(); - } + mTabsAdapter.addTab(DecryptMessageFragment.class, + mMessageFragmentBundle, getString(R.string.label_message)); + mTabsAdapter.addTab(DecryptFileFragment.class, + mFileFragmentBundle, getString(R.string.label_file)); + mViewPager.setCurrentItem(mSwitchToTab); } + /** * Handles all actions with this intent * @@ -316,22 +133,26 @@ public class DecryptActivity extends DrawerActivity { * Main Actions */ if (ACTION_DECRYPT.equals(action) && textData != null) { - Log.d(Constants.TAG, "textData null, matching text ..."); + Log.d(Constants.TAG, "textData not null, matching text ..."); Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData); if (matcher.matches()) { Log.d(Constants.TAG, "PGP_MESSAGE matched"); textData = matcher.group(1); // replace non breakable spaces textData = textData.replaceAll("\\xa0", " "); - mMessage.setText(textData); + + mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData); + mSwitchToTab = PAGER_TAB_MESSAGE; } else { - matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(textData); + matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(textData); if (matcher.matches()) { - Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched"); + Log.d(Constants.TAG, "PGP_CLEARTEXT_SIGNATURE matched"); textData = matcher.group(1); // replace non breakable spaces textData = textData.replaceAll("\\xa0", " "); - mMessage.setText(textData); + + mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData); + mSwitchToTab = PAGER_TAB_MESSAGE; } else { Log.d(Constants.TAG, "Nothing matched!"); } @@ -341,17 +162,12 @@ public class DecryptActivity extends DrawerActivity { String path = FileHelper.getPath(this, uri); if (path != null) { - mInputFilename = path; - mFilename.setText(mInputFilename); - guessOutputFilename(); - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceFile) { - mSource.showNext(); - } + mFileFragmentBundle.putString(DecryptFileFragment.ARG_FILENAME, path); + mSwitchToTab = PAGER_TAB_FILE; } else { Log.e(Constants.TAG, - "Direct binary data without actual file in filesystem is not supported. Please use the Remote Service API!"); + "Direct binary data without actual file in filesystem is not supported. " + + "Please use the Remote Service API!"); Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG) .show(); // end activity @@ -363,428 +179,4 @@ public class DecryptActivity extends DrawerActivity { } } - private void guessOutputFilename() { - mInputFilename = mFilename.getText().toString(); - File file = new File(mInputFilename); - String filename = file.getName(); - if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { - filename = filename.substring(0, filename.length() - 4); - } - mOutputFilename = Constants.Path.APP_DIR + "/" + filename; - } - - private void updateSource() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mSourceLabel.setText(R.string.label_file); - mDecryptButton.setText(getString(R.string.btn_decrypt)); - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - mDecryptButton.setText(getString(R.string.btn_decrypt)); - break; - } - - default: { - break; - } - } - } - - private void decryptClicked() { - if (mSource.getCurrentView().getId() == R.id.sourceFile) { - mDecryptTarget = Id.target.file; - } else { - mDecryptTarget = Id.target.message; - } - initiateDecryption(); - } - - private void initiateDecryption() { - if (mDecryptTarget == Id.target.file) { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - guessOutputFilename(); - } - - if (mInputFilename.equals("")) { - AppMsg.makeText(this, R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); - return; - } - - if (mInputFilename.startsWith("file")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - AppMsg.makeText( - this, - getString(R.string.error_message, - getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) - .show(); - return; - } - } - } - - if (mDecryptTarget == Id.target.message) { - String messageData = mMessage.getText().toString(); - Matcher matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(messageData); - if (matcher.matches()) { - mSignedOnly = true; - decryptStart(); - return; - } - } - - // else treat it as an decrypted message/file - mSignedOnly = false; - - getDecryptionKeyFromInputStream(); - - // if we need a symmetric passphrase or a passphrase to use a secret key ask for it - if (mSecretKeyId == Id.key.symmetric - || PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) { - showPassphraseDialog(); - } else { - if (mDecryptTarget == Id.target.file) { - askForOutputFilename(); - } else { // mDecryptTarget == Id.target.message - decryptStart(); - } - } - } - - /** - * Shows passphrase dialog to cache a new passphrase the user enters for using it later for - * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks - * for a symmetric passphrase - */ - private void showPassphraseDialog() { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - if (mDecryptTarget == Id.target.file) { - askForOutputFilename(); - } else { - decryptStart(); - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this, - messenger, mSecretKeyId); - - passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); - } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); - // send message to handler to start encryption directly - returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); - } - } - - /** - * TODO: Rework function, remove global variables - */ - private void getDecryptionKeyFromInputStream() { - InputStream inStream = null; - if (mContentUri != null) { - try { - inStream = getContentResolver().openInputStream(mContentUri); - } catch (FileNotFoundException e) { - Log.e(Constants.TAG, "File not found!", e); - AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()), - AppMsg.STYLE_ALERT).show(); - } - } else if (mDecryptTarget == Id.target.file) { - // check if storage is ready - if (!FileHelper.isStorageMounted(mInputFilename)) { - AppMsg.makeText(this, getString(R.string.error_external_storage_not_ready), - AppMsg.STYLE_ALERT).show(); - return; - } - - try { - inStream = new BufferedInputStream(new FileInputStream(mInputFilename)); - } catch (FileNotFoundException e) { - Log.e(Constants.TAG, "File not found!", e); - AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()), - AppMsg.STYLE_ALERT).show(); - } finally { - try { - if (inStream != null) { - inStream.close(); - } - } catch (Exception e) { - } - } - } else { - inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes()); - } - - // get decryption key for this inStream - try { - try { - if (inStream.markSupported()) { - inStream.mark(200); // should probably set this to the max size of two pgpF - // objects, if it even needs to be anything other than 0. - } - mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream); - if (mSecretKeyId == Id.key.none) { - throw new PgpGeneralException(getString(R.string.error_no_secret_key_found)); - } - mAssumeSymmetricEncryption = false; - } catch (NoAsymmetricEncryptionException e) { - if (inStream.markSupported()) { - inStream.reset(); - } - mSecretKeyId = Id.key.symmetric; - if (!PgpDecryptVerify.hasSymmetricEncryption(this, inStream)) { - throw new PgpGeneralException( - getString(R.string.error_no_known_encryption_found)); - } - mAssumeSymmetricEncryption = true; - } - } catch (Exception e) { - AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()), - AppMsg.STYLE_ALERT).show(); - } - } - - private void replyClicked() { - Intent intent = new Intent(this, EncryptActivity.class); - intent.setAction(EncryptActivity.ACTION_ENCRYPT); - String data = mMessage.getText().toString(); - data = data.replaceAll("(?m)^", "> "); - data = "\n\n" + data; - intent.putExtra(EncryptActivity.EXTRA_TEXT, data); - intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId); - intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[]{mSignatureKeyId}); - startActivity(intent); - } - - private void askForOutputFilename() { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - decryptStart(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - mFileDialog = FileDialogFragment.newInstance(messenger, - getString(R.string.title_decrypt_to_file), - getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null); - - mFileDialog.show(getSupportFragmentManager(), "fileDialog"); - } - - private void lookupUnknownKey(long unknownKeyId) { - Intent intent = new Intent(this, ImportKeysActivity.class); - intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); - intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId); - startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY); - } - - private void decryptStart() { - Log.d(Constants.TAG, "decryptStart"); - - // Send all information needed to service to decrypt in other thread - Intent intent = new Intent(this, KeychainIntentService.class); - - // fill values for this action - Bundle data = new Bundle(); - - intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); - - // choose action based on input: decrypt stream, file or bytes - if (mContentUri != null) { - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_STREAM); - - data.putParcelable(KeychainIntentService.ENCRYPT_PROVIDER_URI, mContentUri); - } else if (mDecryptTarget == Id.target.file) { - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); - - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename); - - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); - } else { - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); - - String message = mMessage.getText().toString(); - data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, message.getBytes()); - } - - data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId); - - data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary); - data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after encrypting is done in ApgService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard ApgHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get returned data bundle - Bundle returnData = message.getData(); - - mSignatureKeyId = 0; - mSignatureLayout.setVisibility(View.GONE); - - AppMsg.makeText(DecryptActivity.this, R.string.decryption_successful, - AppMsg.STYLE_INFO).show(); - if (mReturnResult) { - Intent intent = new Intent(); - intent.putExtras(returnData); - setResult(RESULT_OK, intent); - finish(); - return; - } - - switch (mDecryptTarget) { - case Id.target.message: - String decryptedMessage = returnData - .getString(KeychainIntentService.RESULT_DECRYPTED_STRING); - mMessage.setText(decryptedMessage); - mMessage.setHorizontallyScrolling(false); - - break; - - case Id.target.file: - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); - } - break; - - default: - // shouldn't happen - break; - - } - - PgpDecryptVerifyResult decryptVerifyResult = - returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT); - - OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); - - if (signatureResult != null) { - - String userId = signatureResult.getUserId(); - mSignatureKeyId = signatureResult.getKeyId(); - mUserIdRest.setText("id: " - + PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId)); - if (userId == null) { - userId = getResources().getString(R.string.user_id_no_name); - } - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mUserIdRest.setText("<" + chunks[1]); - } - mUserId.setText(userId); - - switch (signatureResult.getStatus()) { - case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { - mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); - mLookupKey.setVisibility(View.GONE); - break; - } - - // TODO! -// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { -// break; -// } - - case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: { - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - mLookupKey.setVisibility(View.VISIBLE); - AppMsg.makeText(DecryptActivity.this, - R.string.unknown_signature, - AppMsg.STYLE_ALERT).show(); - break; - } - - default: { - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - mLookupKey.setVisibility(View.GONE); - break; - } - } - mSignatureLayout.setVisibility(View.VISIBLE); - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(this); - - // start service with intent - startService(intent); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case RESULT_CODE_FILE: { - if (resultCode == RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(this, data.getData()); - Log.d(Constants.TAG, "path=" + path); - - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); - } - } - return; - } - - // this request is returned after LookupUnknownKeyDialogFragment started - // ImportKeysActivity and user looked uo key - case RESULT_CODE_LOOKUP_KEY: { - Log.d(Constants.TAG, "Returning from Lookup Key..."); - if (resultCode == RESULT_OK) { - // decrypt again - decryptStart(); - } - return; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - - break; - } - } - } - } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java new file mode 100644 index 000000000..492c0cf29 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.EditText; + +import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.FileHelper; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.File; + +public class DecryptFileFragment extends DecryptFragment { + public static final String ARG_FILENAME = "filename"; + + private static final int RESULT_CODE_FILE = 0x00007003; + + // view + private EditText mFilename; + private CheckBox mDeleteAfter; + private BootstrapButton mBrowse; + private BootstrapButton mDecryptButton; + + private String mInputFilename = null; + private String mOutputFilename = null; + + private FileDialogFragment mFileDialog; + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false); + + mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename); + mBrowse = (BootstrapButton) view.findViewById(R.id.decrypt_file_browse); + mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption); + mDecryptButton = (BootstrapButton) view.findViewById(R.id.decrypt_file_action_decrypt); + mBrowse.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", + RESULT_CODE_FILE); + } + }); + mDecryptButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + decryptAction(); + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + String filename = getArguments().getString(ARG_FILENAME); + if (filename != null) { + mFilename.setText(filename); + } + } + + private void guessOutputFilename() { + mInputFilename = mFilename.getText().toString(); + File file = new File(mInputFilename); + String filename = file.getName(); + if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { + filename = filename.substring(0, filename.length() - 4); + } + mOutputFilename = Constants.Path.APP_DIR + "/" + filename; + } + + private void decryptAction() { + String currentFilename = mFilename.getText().toString(); + if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + guessOutputFilename(); + } + + if (mInputFilename.equals("")) { + AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); + return; + } + + if (mInputFilename.startsWith("file")) { + File file = new File(mInputFilename); + if (!file.exists() || !file.isFile()) { + AppMsg.makeText( + getActivity(), + getString(R.string.error_message, + getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) + .show(); + return; + } + } + + askForOutputFilename(); + } + + private void askForOutputFilename() { + // Message is received after passphrase is cached + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == FileDialogFragment.MESSAGE_OKAY) { + Bundle data = message.getData(); + mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + decryptStart(null); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + mFileDialog = FileDialogFragment.newInstance(messenger, + getString(R.string.title_decrypt_to_file), + getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null); + + mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog"); + } + + @Override + protected void decryptStart(String passphrase) { + Log.d(Constants.TAG, "decryptStart"); + + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + + intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); + + // data + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); + + Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" + + mOutputFilename); + + data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + + data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after encrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle returnData = message.getData(); + + PgpDecryptVerifyResult decryptVerifyResult = + returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT); + + if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { + showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded()); + } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED == + decryptVerifyResult.getStatus()) { + showPassphraseDialog(Id.key.symmetric); + } else { + AppMsg.makeText(getActivity(), R.string.decryption_successful, + AppMsg.STYLE_INFO).show(); + + if (mDeleteAfter.isChecked()) { + // Create and show dialog to delete original file + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + } + + OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); + + // display signature result in activity + onSignatureResult(signatureResult); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case RESULT_CODE_FILE: { + if (resultCode == Activity.RESULT_OK && data != null) { + try { + String path = FileHelper.getPath(getActivity(), data.getData()); + Log.d(Constants.TAG, "path=" + path); + + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java new file mode 100644 index 000000000..1c465f55c --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v4.app.Fragment; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; + +public class DecryptFragment extends Fragment { + private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006; + + protected long mSignatureKeyId = 0; + + protected RelativeLayout mSignatureLayout = null; + protected ImageView mSignatureStatusImage = null; + protected TextView mUserId = null; + protected TextView mUserIdRest = null; + + protected BootstrapButton mLookupKey = null; + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mSignatureLayout = (RelativeLayout) getView().findViewById(R.id.signature); + mSignatureStatusImage = (ImageView) getView().findViewById(R.id.ic_signature_status); + mUserId = (TextView) getView().findViewById(R.id.mainUserId); + mUserIdRest = (TextView) getView().findViewById(R.id.mainUserIdRest); + mLookupKey = (BootstrapButton) getView().findViewById(R.id.lookup_key); + mLookupKey.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + lookupUnknownKey(mSignatureKeyId); + } + }); + mSignatureLayout.setVisibility(View.GONE); + mSignatureLayout.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + lookupUnknownKey(mSignatureKeyId); + } + }); + } + + private void lookupUnknownKey(long unknownKeyId) { + Intent intent = new Intent(getActivity(), ImportKeysActivity.class); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); + intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId); + startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + + case RESULT_CODE_LOOKUP_KEY: { + if (resultCode == Activity.RESULT_OK) { + // TODO: generate new OpenPgpSignatureResult and display it + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + } + + protected void onSignatureResult(OpenPgpSignatureResult signatureResult) { + mSignatureKeyId = 0; + mSignatureLayout.setVisibility(View.GONE); + if (signatureResult != null) { + + mSignatureKeyId = signatureResult.getKeyId(); + + String userId = signatureResult.getUserId(); + String[] userIdSplit = PgpKeyHelper.splitUserId(userId); + if (userIdSplit[0] != null) { + mUserId.setText(userId); + } else { + mUserId.setText(R.string.user_id_no_name); + } + if (userIdSplit[1] != null) { + mUserIdRest.setText(userIdSplit[1]); + } else { + mUserIdRest.setText(getString(R.string.label_key_id) + ": " + + PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId)); + } + + switch (signatureResult.getStatus()) { + case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { + mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); + mLookupKey.setVisibility(View.GONE); + break; + } + + // TODO! +// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { +// break; +// } + + case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: { + mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + mLookupKey.setVisibility(View.VISIBLE); + AppMsg.makeText(getActivity(), + R.string.unknown_signature, + AppMsg.STYLE_ALERT).show(); + break; + } + + default: { + mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + mLookupKey.setVisibility(View.GONE); + break; + } + } + mSignatureLayout.setVisibility(View.VISIBLE); + } + } + + protected void showPassphraseDialog(long keyId) { + PassphraseDialogFragment.show(getActivity(), keyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + String passphrase = + message.getData().getString(PassphraseDialogFragment.MESSAGE_DATA_PASSPHRASE); + decryptStart(passphrase); + } + } + }); + } + + /** + * Should be overridden by MessageFragment and FileFragment to start actual decryption + * + * @param passphrase + */ + protected void decryptStart(String passphrase) { + + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java new file mode 100644 index 000000000..2169bbd77 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; + +import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; +import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.regex.Matcher; + +public class DecryptMessageFragment extends DecryptFragment { + public static final String ARG_CIPHERTEXT = "ciphertext"; + + // view + private EditText mMessage; + private BootstrapButton mDecryptButton; + private BootstrapButton mDecryptFromCLipboardButton; + + // model + private String mCiphertext; + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.decrypt_message_fragment, container, false); + + mMessage = (EditText) view.findViewById(R.id.message); + mDecryptButton = (BootstrapButton) view.findViewById(R.id.action_decrypt); + mDecryptFromCLipboardButton = (BootstrapButton) view.findViewById(R.id.action_decrypt_from_clipboard); + mDecryptButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + decryptClicked(); + } + }); + mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + decryptFromClipboardClicked(); + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + String ciphertext = getArguments().getString(ARG_CIPHERTEXT); + if (ciphertext != null) { + mMessage.setText(ciphertext); + decryptStart(null); + } + } + + private void decryptClicked() { + mCiphertext = mMessage.getText().toString(); + decryptStart(null); + } + + private void decryptFromClipboardClicked() { + CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); + + // only decrypt if clipboard content is available and a pgp message or cleartext signature + if (clipboardText != null) { + Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText); + if (!matcher.matches()) { + matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText); + } + if (matcher.matches()) { + mCiphertext = matcher.group(1); + decryptStart(null); + } else { + AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO) + .show(); + } + } else { + AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO) + .show(); + } + } + + @Override + protected void decryptStart(String passphrase) { + Log.d(Constants.TAG, "decryptStart"); + + // Send all information needed to service to decrypt in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + // fill values for this action + Bundle data = new Bundle(); + + intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); + + // data + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); + data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes()); + data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after encrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle returnData = message.getData(); + + PgpDecryptVerifyResult decryptVerifyResult = + returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT); + + if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) { + showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded()); + } else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED == + decryptVerifyResult.getStatus()) { + showPassphraseDialog(Id.key.symmetric); + } else { + AppMsg.makeText(getActivity(), R.string.decryption_successful, + AppMsg.STYLE_INFO).show(); + + byte[] decryptedMessage = returnData + .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); + mMessage.setText(new String(decryptedMessage)); + mMessage.setHorizontallyScrolling(false); + + OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult(); + + // display signature result in activity + onSignatureResult(signatureResult); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java index c0fd53007..f81224380 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java @@ -21,19 +21,26 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.graphics.Color; import android.os.Bundle; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarActivity; -import android.view.*; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; + import com.beardedhen.androidbootstrap.FontAwesomeText; + +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity; public class DrawerActivity extends ActionBarActivity { private DrawerLayout mDrawerLayout; @@ -42,10 +49,8 @@ public class DrawerActivity extends ActionBarActivity { private CharSequence mDrawerTitle; private CharSequence mTitle; + private boolean mIsDrawerLocked = false; - private static Class[] mItemsClass = new Class[]{KeyListActivity.class, - EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class, - RegisteredAppsListActivity.class}; private Class mSelectedItem; private static final int MENU_ID_PREFERENCE = 222; @@ -55,10 +60,22 @@ public class DrawerActivity extends ActionBarActivity { mDrawerTitle = getString(R.string.app_name); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerList = (ListView) findViewById(R.id.left_drawer); + ViewGroup viewGroup = (ViewGroup) findViewById(R.id.content_frame); + int leftMarginLoaded = ((ViewGroup.MarginLayoutParams) viewGroup.getLayoutParams()).leftMargin; + int leftMarginInTablets = (int) getResources().getDimension(R.dimen.drawer_size); + int errorInMarginAllowed = 5; - // set a custom shadow that overlays the main content when the drawer - // opens - mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + // if the left margin of the loaded layout is close to the + // one used in tablets then set drawer as open and locked + if (Math.abs(leftMarginLoaded - leftMarginInTablets) < errorInMarginAllowed) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerList); + mDrawerLayout.setScrimColor(Color.TRANSPARENT); + mIsDrawerLocked = true; + } else { + // set a custom shadow that overlays the main content when the drawer opens + mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + mIsDrawerLocked = false; + } NavItem mItemIconTexts[] = new NavItem[]{ new NavItem("fa-user", getString(R.string.nav_contacts)), @@ -73,8 +90,11 @@ public class DrawerActivity extends ActionBarActivity { mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); // enable ActionBar app icon to behave as action to toggle nav drawer - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); + // if the drawer is not locked + if (!mIsDrawerLocked) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + } // ActionBarDrawerToggle ties together the the proper interactions // between the sliding drawer and the action bar app icon @@ -86,19 +106,8 @@ public class DrawerActivity extends ActionBarActivity { ) { public void onDrawerClosed(View view) { getSupportActionBar().setTitle(mTitle); - // creates call to onPrepareOptionsMenu() - supportInvalidateOptionsMenu(); - // call intent activity if selected - if (mSelectedItem != null) { - finish(); - overridePendingTransition(0, 0); - - Intent intent = new Intent(DrawerActivity.this, mSelectedItem); - startActivity(intent); - // disable animation of activity start - overridePendingTransition(0, 0); - } + callIntentForDrawerItem(mSelectedItem); } public void onDrawerOpened(View drawerView) { @@ -108,33 +117,56 @@ public class DrawerActivity extends ActionBarActivity { supportInvalidateOptionsMenu(); } }; - mDrawerLayout.setDrawerListener(mDrawerToggle); - // if (savedInstanceState == null) { - // selectItem(0); - // } + if (!mIsDrawerLocked) { + mDrawerLayout.setDrawerListener(mDrawerToggle); + } else { + // If the drawer is locked open make it un-focusable + // so that it doesn't consume all the Back button presses + mDrawerLayout.setFocusableInTouchMode(false); + } + } + + /** + * Uses startActivity to call the Intent of the given class + * + * @param drawerItem the class of the drawer item you want to load. Based on Constants.DrawerItems.* + */ + public void callIntentForDrawerItem(Class drawerItem) { + // creates call to onPrepareOptionsMenu() + supportInvalidateOptionsMenu(); + + // call intent activity if selected + if (drawerItem != null) { + finish(); + overridePendingTransition(0, 0); + + Intent intent = new Intent(this, drawerItem); + startActivity(intent); + + // disable animation of activity start + overridePendingTransition(0, 0); + } } @Override public boolean onCreateOptionsMenu(Menu menu) { + if (mDrawerToggle == null) { + return super.onCreateOptionsMenu(menu); + } + menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences); menu.add(42, MENU_ID_HELP, 101, R.string.menu_help); return super.onCreateOptionsMenu(menu); } - /* Called whenever we call invalidateOptionsMenu() */ - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // If the nav drawer is open, hide action items related to the content - // view - boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); - // menu.findItem(R.id.action_websearch).setVisible(!drawerOpen); - return super.onPrepareOptionsMenu(menu); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { + if (mDrawerToggle == null) { + return super.onOptionsItemSelected(item); + } + // The action bar home/up action should open or close the drawer. // ActionBarDrawerToggle will take care of this. if (mDrawerToggle.onOptionsItemSelected(item)) { @@ -155,26 +187,11 @@ public class DrawerActivity extends ActionBarActivity { default: return super.onOptionsItemSelected(item); } - - // Handle action buttons - // switch (item.getItemId()) { - // case R.id.action_websearch: - // // create intent to perform web search for this planet - // Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); - // intent.putExtra(SearchManager.QUERY, getSupportActionBar().getTitle()); - // // catch event that there's no activity to handle intent - // if (intent.resolveActivity(getPackageManager()) != null) { - // startActivity(intent); - // } else { - // Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show(); - // } - // return true; - // default: - // return super.onOptionsItemSelected(item); - // } } - /* The click listener for ListView in the navigation drawer */ + /** + * The click listener for ListView in the navigation drawer + */ private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { @@ -185,10 +202,18 @@ public class DrawerActivity extends ActionBarActivity { private void selectItem(int position) { // update selected item and title, then close the drawer mDrawerList.setItemChecked(position, true); - // setTitle(mDrawerTitles[position]); - mDrawerLayout.closeDrawer(mDrawerList); // set selected class - mSelectedItem = mItemsClass[position]; + mSelectedItem = Constants.DrawerItems.ARRAY[position]; + + // setTitle(mDrawerTitles[position]); + // If drawer isn't locked just close the drawer and + // it will move to the selected item by itself (via drawer toggle listener) + if (!mIsDrawerLocked) { + mDrawerLayout.closeDrawer(mDrawerList); + // else move to the selected item yourself + } else { + callIntentForDrawerItem(mSelectedItem); + } } /** @@ -199,14 +224,18 @@ public class DrawerActivity extends ActionBarActivity { protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. - mDrawerToggle.syncState(); + if (mDrawerToggle != null) { + mDrawerToggle.syncState(); + } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Pass any configuration change to the drawer toggles - mDrawerToggle.onConfigurationChanged(newConfig); + if (mDrawerToggle != null) { + mDrawerToggle.onConfigurationChanged(newConfig); + } } private class NavItem { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index edf980773..60bababd1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Dominik Schürmann + * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2010 Thialfihar * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; @@ -27,32 +28,45 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.support.v4.app.ActivityCompat; import android.support.v7.app.ActionBarActivity; -import android.view.*; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.LinearLayout; import android.widget.Toast; + import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; +import org.sufficientlysecure.keychain.ui.widget.Editor; +import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.ui.widget.KeyEditor; import org.sufficientlysecure.keychain.ui.widget.SectionView; import org.sufficientlysecure.keychain.ui.widget.UserIdEditor; @@ -61,9 +75,10 @@ import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; import java.util.GregorianCalendar; +import java.util.List; import java.util.Vector; -public class EditKeyActivity extends ActionBarActivity { +public class EditKeyActivity extends ActionBarActivity implements EditorListener { // Actions for internal use only: public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY"; @@ -74,10 +89,6 @@ public class EditKeyActivity extends ActionBarActivity { public static final String EXTRA_NO_PASSPHRASE = "no_passphrase"; public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys"; - // results when saving key - public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id"; - public static final String RESULT_EXTRA_USER_ID = "user_id"; - // EDIT private Uri mDataUri; @@ -87,9 +98,11 @@ public class EditKeyActivity extends ActionBarActivity { private SectionView mKeysView; private String mCurrentPassphrase = null; - private String mNewPassPhrase = null; - private String mSavedNewPassPhrase = null; - private boolean mIsPassPhraseSet; + private String mNewPassphrase = null; + private String mSavedNewPassphrase = null; + private boolean mIsPassphraseSet; + private boolean mNeedsSaving; + private boolean mIsBrandNewKeyring = false; private BootstrapButton mChangePassphrase; @@ -102,12 +115,37 @@ public class EditKeyActivity extends ActionBarActivity { ExportHelper mExportHelper; + public boolean needsSaving() { + mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving(); + mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving(); + mNeedsSaving |= hasPassphraseChanged(); + mNeedsSaving |= mIsBrandNewKeyring; + return mNeedsSaving; + } + + + public void somethingChanged() { + ActivityCompat.invalidateOptionsMenu(this); + } + + public void onDeleted(Editor e, boolean wasNewItem) { + somethingChanged(); + } + + public void onEdited() { + somethingChanged(); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mExportHelper = new ExportHelper(this); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setIcon(android.R.color.transparent); + getSupportActionBar().setHomeButtonEnabled(true); + mUserIds = new Vector(); mKeys = new Vector(); mKeysUsages = new Vector(); @@ -128,24 +166,10 @@ public class EditKeyActivity extends ActionBarActivity { * @param intent */ private void handleActionCreateKey(Intent intent) { - // Inflate a "Save"/"Cancel" custom action bar - ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - saveClicked(); - } - }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { - @Override - public void onClick(View v) { - cancelClicked(); - } - } - ); - Bundle extras = intent.getExtras(); mCurrentPassphrase = ""; + mIsBrandNewKeyring = true; if (extras != null) { // if userId is given, prefill the fields @@ -180,7 +204,7 @@ public class EditKeyActivity extends ActionBarActivity { serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after generating is done in ApgService + // Message is received after generating is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( this, getResources().getQuantityString(R.plurals.progress_generating, 1), ProgressDialog.STYLE_HORIZONTAL, true, @@ -197,28 +221,28 @@ public class EditKeyActivity extends ActionBarActivity { @Override public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { // get new key from data bundle returned from service Bundle data = message.getData(); - PGPSecretKey masterKey = (PGPSecretKey) PgpConversionHelper - .BytesToPGPSecretKey(data + + ArrayList newKeys = + PgpConversionHelper.BytesToPGPSecretKeyList(data .getByteArray(KeychainIntentService.RESULT_NEW_KEY)); - PGPSecretKey subKey = (PGPSecretKey) PgpConversionHelper - .BytesToPGPSecretKey(data - .getByteArray(KeychainIntentService.RESULT_NEW_KEY2)); - // add master key - mKeys.add(masterKey); - mKeysUsages.add(Id.choice.usage.sign_only); //TODO: get from key flags + ArrayList keyUsageFlags = data.getIntegerArrayList( + KeychainIntentService.RESULT_KEY_USAGES); - // add sub key - mKeys.add(subKey); - mKeysUsages.add(Id.choice.usage.encrypt_only); //TODO: get from key flags + if (newKeys.size() == keyUsageFlags.size()) { + for (int i = 0; i < newKeys.size(); ++i) { + mKeys.add(newKeys.get(i)); + mKeysUsages.add(keyUsageFlags.get(i)); + } + } - buildLayout(); + buildLayout(true); } } }; @@ -234,7 +258,7 @@ public class EditKeyActivity extends ActionBarActivity { } } } else { - buildLayout(); + buildLayout(false); } } @@ -244,67 +268,16 @@ public class EditKeyActivity extends ActionBarActivity { * @param intent */ private void handleActionEditKey(Intent intent) { - // Inflate a "Save"/"Cancel" custom action bar - ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - saveClicked(); - } - }); - mDataUri = intent.getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); finish(); - return; } else { Log.d(Constants.TAG, "uri: " + mDataUri); // get master key id using row id long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); - - mMasterCanSign = ProviderHelper.getMasterKeyCanSign(this, mDataUri); - finallyEdit(masterKeyId, mMasterCanSign); - } - } - - private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - String passphrase = PassphraseCacheService.getCachedPassphrase( - EditKeyActivity.this, masterKeyId); - mCurrentPassphrase = passphrase; - finallySaveClicked(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( - EditKeyActivity.this, messenger, masterKeyId); - - passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); - } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key!"); - // send message to handler to start encryption directly - returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // show menu only on edit - if (mDataUri != null) { - return super.onPrepareOptionsMenu(menu); - } else { - return false; + finallyEdit(masterKeyId); } } @@ -312,45 +285,66 @@ public class EditKeyActivity extends ActionBarActivity { public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.key_edit, menu); + //totally get rid of some actions for new keys + if (mDataUri == null) { + MenuItem mButton = menu.findItem(R.id.menu_key_edit_export_file); + mButton.setVisible(false); + mButton = menu.findItem(R.id.menu_key_edit_delete); + mButton.setVisible(false); + } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case android.R.id.home: + cancelClicked(); + // TODO: why isn't this triggered on my tablet - one of many ui problems + // I've had with this device. A code compatibility issue or a Samsung fail? + return true; case R.id.menu_key_edit_cancel: cancelClicked(); return true; case R.id.menu_key_edit_export_file: - long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())}; - mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC); + if (needsSaving()) { + Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show(); + } else { + long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); + mExportHelper.showExportKeysDialog( + new long[] { masterKeyId }, Constants.Path.APP_DIR_FILE_SEC, true); + return true; + } + return true; + case R.id.menu_key_edit_delete: + Uri convertUri = KeyRingData.buildSecretKeyRingUri(mDataUri); + // Message is received after key is deleted + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { + setResult(RESULT_CANCELED); + finish(); + } + }}; + mExportHelper.deleteKey(convertUri, returnHandler); return true; - case R.id.menu_key_edit_delete: { - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { - setResult(RESULT_CANCELED); - finish(); - } - } - }; - mExportHelper.deleteKey(mDataUri, Id.type.secret_key, returnHandler); + case R.id.menu_key_edit_save: + saveClicked(); return true; - } } return super.onOptionsItemSelected(item); } @SuppressWarnings("unchecked") - private void finallyEdit(final long masterKeyId, final boolean masterCanSign) { + private void finallyEdit(final long masterKeyId) { if (masterKeyId != 0) { PGPSecretKey masterKey = null; - mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId); + mKeyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId); if (mKeyRing != null) { - masterKey = PgpKeyHelper.getMasterKey(mKeyRing); + masterKey = mKeyRing.getSecretKey(); + mMasterCanSign = PgpKeyHelper.isCertificationKey(mKeyRing.getSecretKey()); for (PGPSecretKey key : new IterableIterator(mKeyRing.getSecretKeys())) { mKeys.add(key); mKeysUsages.add(-1); // get usage when view is created @@ -358,20 +352,29 @@ public class EditKeyActivity extends ActionBarActivity { } else { Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId); Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show(); + // TODO } if (masterKey != null) { + boolean isSet = false; for (String userId : new IterableIterator(masterKey.getUserIDs())) { Log.d(Constants.TAG, "Added userId " + userId); + if (!isSet) { + isSet = true; + String[] parts = PgpKeyHelper.splitUserId(userId); + if (parts[0] != null) { + setTitle(parts[0]); + } + } mUserIds.add(userId); } } } mCurrentPassphrase = ""; + buildLayout(false); - buildLayout(); - mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); - if (!mIsPassPhraseSet) { + mIsPassphraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); + if (!mIsPassphraseSet) { // check "no passphrase" checkbox and remove button mNoPassphrase.setChecked(true); mChangePassphrase.setVisibility(View.GONE); @@ -390,10 +393,11 @@ public class EditKeyActivity extends ActionBarActivity { Bundle data = message.getData(); // set new returned passphrase! - mNewPassPhrase = data + mNewPassphrase = data .getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE); - updatePassPhraseButtonText(); + updatePassphraseButtonText(); + somethingChanged(); } } }; @@ -402,7 +406,7 @@ public class EditKeyActivity extends ActionBarActivity { Messenger messenger = new Messenger(returnHandler); // set title based on isPassphraseSet() - int title = -1; + int title; if (isPassphraseSet()) { title = R.string.title_change_passphrase; } else { @@ -418,30 +422,37 @@ public class EditKeyActivity extends ActionBarActivity { /** * Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user * id and key. + * + * @param newKeys */ - private void buildLayout() { + private void buildLayout(boolean newKeys) { setContentView(R.layout.edit_key_activity); // find views mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase); mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); - // Build layout based on given userIds and keys + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container); + if (mIsPassphraseSet) { + mChangePassphrase.setText(getString(R.string.btn_change_passphrase)); + } mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); mUserIdsView.setType(Id.type.user_id); - mUserIdsView.setCanEdit(mMasterCanSign); + mUserIdsView.setCanBeEdited(mMasterCanSign); mUserIdsView.setUserIds(mUserIds); + mUserIdsView.setEditorListener(this); container.addView(mUserIdsView); mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); mKeysView.setType(Id.type.key); - mKeysView.setCanEdit(mMasterCanSign); - mKeysView.setKeys(mKeys, mKeysUsages); + mKeysView.setCanBeEdited(mMasterCanSign); + mKeysView.setKeys(mKeys, mKeysUsages, newKeys); + mKeysView.setEditorListener(this); container.addView(mKeysView); - updatePassPhraseButtonText(); + updatePassphraseButtonText(); mChangePassphrase.setOnClickListener(new OnClickListener() { public void onClick(View v) { @@ -449,20 +460,21 @@ public class EditKeyActivity extends ActionBarActivity { } }); - // disable passphrase when no passphrase checkobox is checked! + // disable passphrase when no passphrase checkbox is checked! mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { // remove passphrase - mSavedNewPassPhrase = mNewPassPhrase; - mNewPassPhrase = ""; + mSavedNewPassphrase = mNewPassphrase; + mNewPassphrase = ""; mChangePassphrase.setVisibility(View.GONE); } else { - mNewPassPhrase = mSavedNewPassPhrase; + mNewPassphrase = mSavedNewPassphrase; mChangePassphrase.setVisibility(View.VISIBLE); } + somethingChanged(); } }); } @@ -477,37 +489,116 @@ public class EditKeyActivity extends ActionBarActivity { public boolean isPassphraseSet() { if (mNoPassphrase.isChecked()) { return true; - } else if ((mIsPassPhraseSet) - || (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) { + } else if ((mIsPassphraseSet) + || (mNewPassphrase != null && !mNewPassphrase.equals(""))) { return true; } else { return false; } } - private void saveClicked() { - long masterKeyId = getMasterKeyId(); - try { - if (!isPassphraseSet()) { - throw new PgpGeneralException(this.getString(R.string.set_a_passphrase)); + public boolean hasPassphraseChanged() { + if (mNoPassphrase != null) { + if (mNoPassphrase.isChecked()) { + return mIsPassphraseSet; + } else { + return (mNewPassphrase != null && !mNewPassphrase.equals("")); } + } else { + return false; + } + } - String passphrase = null; - if (mIsPassPhraseSet) { - passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); - } else { - passphrase = ""; + private void saveClicked() { + final long masterKeyId = getMasterKeyId(); + if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu + try { + if (!isPassphraseSet()) { + throw new PgpGeneralException(this.getString(R.string.set_a_passphrase)); + } + + String passphrase; + if (mIsPassphraseSet) { + passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); + } else { + passphrase = ""; + } + if (passphrase == null) { + PassphraseDialogFragment.show(this, masterKeyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase( + EditKeyActivity.this, masterKeyId); + checkEmptyIDsWanted(); + } + } + }); + } else { + mCurrentPassphrase = passphrase; + checkEmptyIDsWanted(); + } + } catch (PgpGeneralException e) { + Toast.makeText(this, getString(R.string.error_message, e.getMessage()), + Toast.LENGTH_SHORT).show(); } - if (passphrase == null) { - showPassphraseDialog(masterKeyId, mMasterCanSign); - } else { - mCurrentPassphrase = passphrase; - finallySaveClicked(); + } else { + AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show(); + } + } + + private void checkEmptyIDsWanted() { + try { + ArrayList userIDs = getUserIds(mUserIdsView); + List newIDs = mUserIdsView.getNewIDFlags(); + ArrayList originalIDs = mUserIdsView.getOriginalIDs(); + int curID = 0; + for (String userID : userIDs) { + if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) { + AlertDialog.Builder alert = new AlertDialog.Builder( + EditKeyActivity.this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.warning); + alert.setMessage(EditKeyActivity.this.getString(R.string.ask_empty_id_ok)); + + alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + finallySaveClicked(); + } + } + ); + alert.setNegativeButton(this.getString(android.R.string.no), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + } + ); + alert.setCancelable(false); + alert.create().show(); + return; + } + curID++; } } catch (PgpGeneralException e) { - //Toast.makeText(this, getString(R.string.error_message, e.getMessage()), - // Toast.LENGTH_SHORT).show(); + Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage())); + Toast.makeText(this, getString(R.string.error_message, e.getMessage()), + Toast.LENGTH_SHORT).show(); } + finallySaveClicked(); + } + + private boolean[] toPrimitiveArray(final List booleanList) { + final boolean[] primitives = new boolean[booleanList.size()]; + int index = 0; + for (Boolean object : booleanList) { + primitives[index++] = object; + } + return primitives; } private void finallySaveClicked() { @@ -517,42 +608,45 @@ public class EditKeyActivity extends ActionBarActivity { intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING); + SaveKeyringParcel saveParams = new SaveKeyringParcel(); + saveParams.userIDs = getUserIds(mUserIdsView); + saveParams.originalIDs = mUserIdsView.getOriginalIDs(); + saveParams.deletedIDs = mUserIdsView.getDeletedIDs(); + saveParams.newIDs = toPrimitiveArray(mUserIdsView.getNewIDFlags()); + saveParams.primaryIDChanged = mUserIdsView.primaryChanged(); + saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray()); + saveParams.deletedKeys = mKeysView.getDeletedKeys(); + saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView); + saveParams.keysUsages = getKeysUsages(mKeysView); + saveParams.newPassphrase = mNewPassphrase; + saveParams.oldPassphrase = mCurrentPassphrase; + saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray()); + saveParams.keys = getKeys(mKeysView); + saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID(); + + // fill values for this action Bundle data = new Bundle(); - data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE, - mCurrentPassphrase); - data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase); - data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS, - getUserIds(mUserIdsView)); - ArrayList keys = getKeys(mKeysView); - data.putByteArray(KeychainIntentService.SAVE_KEYRING_KEYS, - PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys)); - data.putIntegerArrayList(KeychainIntentService.SAVE_KEYRING_KEYS_USAGES, - getKeysUsages(mKeysView)); - data.putSerializable(KeychainIntentService.SAVE_KEYRING_KEYS_EXPIRY_DATES, - getKeysExpiryDates(mKeysView)); - data.putLong(KeychainIntentService.SAVE_KEYRING_MASTER_KEY_ID, getMasterKeyId()); data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign); + data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after saving is done in ApgService + // Message is received after saving is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { Intent data = new Intent(); - data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, getMasterKeyId()); - ArrayList userIds = null; - try { - userIds = getUserIds(mUserIdsView); - } catch (PgpGeneralException e) { - Log.e(Constants.TAG, "exception while getting user ids", e); - } - data.putExtra(RESULT_EXTRA_USER_ID, userIds.get(0)); + + // return uri pointing to new created key + Uri uri = KeychainContract.KeyRings.buildGenericKeyRingUri( + String.valueOf(getMasterKeyId())); + data.setData(uri); + setResult(RESULT_OK, data); finish(); } @@ -568,14 +662,42 @@ public class EditKeyActivity extends ActionBarActivity { // start service with intent startService(intent); } catch (PgpGeneralException e) { - //Toast.makeText(this, getString(R.string.error_message, e.getMessage()), - // Toast.LENGTH_SHORT).show(); + Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage())); + Toast.makeText(this, getString(R.string.error_message, e.getMessage()), + Toast.LENGTH_SHORT).show(); } } private void cancelClicked() { - setResult(RESULT_CANCELED); - finish(); + if (needsSaving()) { //ask if we want to save + AlertDialog.Builder alert = new AlertDialog.Builder( + EditKeyActivity.this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.warning); + alert.setMessage(EditKeyActivity.this.getString(R.string.ask_save_changed_key)); + + alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + saveClicked(); + } + }); + alert.setNegativeButton(this.getString(android.R.string.no), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + setResult(RESULT_CANCELED); + finish(); + } + }); + alert.setCancelable(false); + alert.create().show(); + } else { + setResult(RESULT_CANCELED); + finish(); + } } /** @@ -592,19 +714,8 @@ public class EditKeyActivity extends ActionBarActivity { boolean gotMainUserId = false; for (int i = 0; i < userIdEditors.getChildCount(); ++i) { UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i); - String userId = null; - try { - userId = editor.getValue(); - } catch (UserIdEditor.NoNameException e) { - throw new PgpGeneralException(this.getString(R.string.error_user_id_needs_a_name)); - } catch (UserIdEditor.NoEmailException e) { - throw new PgpGeneralException( - this.getString(R.string.error_user_id_needs_an_email_address)); - } - - if (userId.equals("")) { - continue; - } + String userId; + userId = editor.getValue(); if (editor.isMainUserId()) { userIds.add(0, userId); @@ -688,7 +799,7 @@ public class EditKeyActivity extends ActionBarActivity { return keysExpiryDates; } - private void updatePassPhraseButtonText() { + private void updatePassphraseButtonText() { mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase) : getString(R.string.btn_set_passphrase)); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 1231b6209..a03c7d797 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -17,49 +17,25 @@ package org.sufficientlysecure.keychain.ui; -import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.*; -import com.beardedhen.androidbootstrap.BootstrapButton; -import com.beardedhen.androidbootstrap.FontAwesomeText; -import com.devspark.appmsg.AppMsg; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; +import android.support.v4.view.PagerTabStrip; +import android.support.v4.view.ViewPager; +import android.widget.Toast; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; -import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; -import org.sufficientlysecure.keychain.util.Choice; +import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.util.Log; -import java.io.File; -import java.util.Vector; - -public class EncryptActivity extends DrawerActivity { +public class EncryptActivity extends DrawerActivity implements + EncryptSymmetricFragment.OnSymmetricKeySelection, + EncryptAsymmetricFragment.OnAsymmetricKeySelection, + EncryptActivityInterface { /* Intents */ public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT"; @@ -74,57 +50,94 @@ public class EncryptActivity extends DrawerActivity { public static final String EXTRA_SIGNATURE_KEY_ID = "signature_key_id"; public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryption_key_ids"; + // view + ViewPager mViewPagerMode; + PagerTabStrip mPagerTabStripMode; + PagerTabStripAdapter mTabsAdapterMode; + ViewPager mViewPagerContent; + PagerTabStrip mPagerTabStripContent; + PagerTabStripAdapter mTabsAdapterContent; + + // tabs + Bundle mAsymmetricFragmentBundle = new Bundle(); + Bundle mSymmetricFragmentBundle = new Bundle(); + Bundle mMessageFragmentBundle = new Bundle(); + Bundle mFileFragmentBundle = new Bundle(); + int mSwitchToMode = PAGER_MODE_ASYMMETRIC; + int mSwitchToContent = PAGER_CONTENT_MESSAGE; + + private static final int PAGER_MODE_ASYMMETRIC = 0; + private static final int PAGER_MODE_SYMMETRIC = 1; + private static final int PAGER_CONTENT_MESSAGE = 0; + private static final int PAGER_CONTENT_FILE = 1; + + // model useb by message and file fragment private long mEncryptionKeyIds[] = null; + private long mSigningKeyId = Id.key.none; + private String mPassphrase; + private String mPassphraseAgain; - private EditText mMessage = null; - private BootstrapButton mSelectKeysButton = null; + @Override + public void onSigningKeySelected(long signingKeyId) { + mSigningKeyId = signingKeyId; + } - private CheckBox mSign = null; - private TextView mMainUserId = null; - private TextView mMainUserIdRest = null; + @Override + public void onEncryptionKeysSelected(long[] encryptionKeyIds) { + mEncryptionKeyIds = encryptionKeyIds; + } - private ViewFlipper mSource = null; - private TextView mSourceLabel = null; - private ImageView mSourcePrevious = null; - private ImageView mSourceNext = null; + @Override + public void onPassphraseUpdate(String passphrase) { + mPassphrase = passphrase; + } - private ViewFlipper mMode = null; - private TextView mModeLabel = null; - private ImageView mModePrevious = null; - private ImageView mModeNext = null; + @Override + public void onPassphraseAgainUpdate(String passphrase) { + mPassphraseAgain = passphrase; + } - private int mEncryptTarget; + @Override + public boolean isModeSymmetric() { + if (PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem()) { + return true; + } else { + return false; + } + } - private EditText mPassphrase = null; - private EditText mPassphraseAgain = null; - private CheckBox mAsciiArmor = null; - private Spinner mFileCompression = null; + @Override + public long getSignatureKey() { + return mSigningKeyId; + } - private EditText mFilename = null; - private CheckBox mDeleteAfter = null; - private CheckBox mShareAfter = null; - private BootstrapButton mBrowse = null; + @Override + public long[] getEncryptionKeys() { + return mEncryptionKeyIds; + } - private String mInputFilename = null; - private String mOutputFilename = null; + @Override + public String getPassphrase() { + return mPassphrase; + } - private Integer mShortAnimationDuration = null; - private boolean mFileAdvancedSettingsVisible = false; - private TextView mFileAdvancedSettings = null; - private LinearLayout mFileAdvancedSettingsContainer = null; - private FontAwesomeText mAdvancedSettingsIcon; - private boolean mAsciiArmorDemand = false; - private boolean mOverrideAsciiArmor = false; + @Override + public String getPassphraseAgain() { + return mPassphraseAgain; + } - private boolean mGenerateSignature = false; - private long mSecretKeyId = Id.key.none; + private void initView() { + mViewPagerMode = (ViewPager) findViewById(R.id.encrypt_pager_mode); + mPagerTabStripMode = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_mode); + mViewPagerContent = (ViewPager) findViewById(R.id.encrypt_pager_content); + mPagerTabStripContent = (PagerTabStrip) findViewById(R.id.encrypt_pager_tab_strip_content); - private FileDialogFragment mFileDialog; - - private BootstrapButton mEncryptShare; - private BootstrapButton mEncryptClipboard; - private BootstrapButton mEncryptFile; + mTabsAdapterMode = new PagerTabStripAdapter(this); + mViewPagerMode.setAdapter(mTabsAdapterMode); + mTabsAdapterContent = new PagerTabStripAdapter(this); + mViewPagerContent.setAdapter(mTabsAdapterContent); + } @Override public void onCreate(Bundle savedInstanceState) { @@ -142,14 +155,17 @@ public class EncryptActivity extends DrawerActivity { // Handle intent actions handleActions(getIntent()); - updateView(); - updateSource(); - updateMode(); + mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, + mAsymmetricFragmentBundle, getString(R.string.label_asymmetric)); + mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, + mSymmetricFragmentBundle, getString(R.string.label_symmetric)); + mViewPagerMode.setCurrentItem(mSwitchToMode); - updateActionBarButtons(); - - // retrieve and cache the system's short animation time - mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); + mTabsAdapterContent.addTab(EncryptMessageFragment.class, + mMessageFragmentBundle, getString(R.string.label_message)); + mTabsAdapterContent.addTab(EncryptFileFragment.class, + mFileFragmentBundle, getString(R.string.label_file)); + mViewPagerContent.setCurrentItem(mSwitchToContent); } /** @@ -190,9 +206,8 @@ public class EncryptActivity extends DrawerActivity { } if (extras.containsKey(EXTRA_ASCII_ARMOR)) { - mAsciiArmorDemand = extras.getBoolean(EXTRA_ASCII_ARMOR, true); - mOverrideAsciiArmor = true; - mAsciiArmor.setChecked(mAsciiArmorDemand); + boolean requestAsciiArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, true); + mFileFragmentBundle.putBoolean(EncryptFileFragment.ARG_ASCII_ARMOR, requestAsciiArmor); } String textData = extras.getString(EXTRA_TEXT); @@ -201,20 +216,19 @@ public class EncryptActivity extends DrawerActivity { long[] encryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); // preselect keys given by intent - preselectKeys(signatureKeyId, encryptionKeyIds); + mAsymmetricFragmentBundle.putLongArray(EncryptAsymmetricFragment.ARG_ENCRYPTION_KEY_IDS, + encryptionKeyIds); + mAsymmetricFragmentBundle.putLong(EncryptAsymmetricFragment.ARG_SIGNATURE_KEY_ID, + signatureKeyId); + mSwitchToMode = PAGER_MODE_ASYMMETRIC; /** * Main Actions */ if (ACTION_ENCRYPT.equals(action) && textData != null) { // encrypt text based on given extra - - mMessage.setText(textData); - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceMessage) { - mSource.showNext(); - } + mMessageFragmentBundle.putString(EncryptMessageFragment.ARG_TEXT, textData); + mSwitchToContent = PAGER_CONTENT_MESSAGE; } else if (ACTION_ENCRYPT.equals(action) && uri != null) { // encrypt file based on Uri @@ -222,17 +236,12 @@ public class EncryptActivity extends DrawerActivity { String path = FileHelper.getPath(this, uri); if (path != null) { - mInputFilename = path; - mFilename.setText(mInputFilename); - - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceFile) { - mSource.showNext(); - } + mFileFragmentBundle.putString(EncryptFileFragment.ARG_FILENAME, path); + mSwitchToContent = PAGER_CONTENT_FILE; } else { Log.e(Constants.TAG, - "Direct binary data without actual file in filesystem is not supported by Intents. Please use the Remote Service API!"); + "Direct binary data without actual file in filesystem is not supported " + + "by Intents. Please use the Remote Service API!"); Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG) .show(); // end activity @@ -244,779 +253,4 @@ public class EncryptActivity extends DrawerActivity { } } - /** - * If an Intent gives a signatureKeyId and/or encryptionKeyIds, preselect those! - * - * @param preselectedSignatureKeyId - * @param preselectedEncryptionKeyIds - */ - private void preselectKeys(long preselectedSignatureKeyId, long[] preselectedEncryptionKeyIds) { - if (preselectedSignatureKeyId != 0) { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, - preselectedSignatureKeyId); - PGPSecretKey masterKey = null; - if (keyRing != null) { - masterKey = PgpKeyHelper.getMasterKey(keyRing); - if (masterKey != null) { - Vector signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing); - if (signKeys.size() > 0) { - mSecretKeyId = masterKey.getKeyID(); - } - } - } - } - - if (preselectedEncryptionKeyIds != null) { - Vector goodIds = new Vector(); - for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) { - PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, - preselectedEncryptionKeyIds[i]); - PGPPublicKey masterKey = null; - if (keyRing == null) { - continue; - } - masterKey = PgpKeyHelper.getMasterKey(keyRing); - if (masterKey == null) { - continue; - } - Vector encryptKeys = PgpKeyHelper.getUsableEncryptKeys(keyRing); - if (encryptKeys.size() == 0) { - continue; - } - goodIds.add(masterKey.getKeyID()); - } - if (goodIds.size() > 0) { - mEncryptionKeyIds = new long[goodIds.size()]; - for (int i = 0; i < goodIds.size(); ++i) { - mEncryptionKeyIds[i] = goodIds.get(i); - } - } - } - } - - /** - * Guess output filename based on input path - * - * @param path - * @return Suggestion for output filename - */ - private String guessOutputFilename(String path) { - // output in the same directory but with additional ending - File file = new File(path); - String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); - String outputFilename = file.getParent() + File.separator + file.getName() + ending; - - return outputFilename; - } - - private void updateSource() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mSourceLabel.setText(R.string.label_file); - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - break; - } - - default: { - break; - } - } - updateActionBarButtons(); - } - - /** - * Update ActionBar buttons based on current selection in view - */ - private void updateActionBarButtons() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mEncryptShare.setVisibility(View.GONE); - mEncryptClipboard.setVisibility(View.GONE); - mEncryptFile.setVisibility(View.VISIBLE); - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - - mEncryptShare.setVisibility(View.VISIBLE); - mEncryptClipboard.setVisibility(View.VISIBLE); - mEncryptFile.setVisibility(View.GONE); - - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - mEncryptShare.setEnabled(true); - mEncryptClipboard.setEnabled(true); - } else { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - if (mSecretKeyId == 0) { - mEncryptShare.setEnabled(false); - mEncryptClipboard.setEnabled(false); - } else { - mEncryptShare.setEnabled(true); - mEncryptClipboard.setEnabled(true); - } - } else { - mEncryptShare.setEnabled(true); - mEncryptClipboard.setEnabled(true); - } - } - break; - } - - default: { - break; - } - } - - } - - private void updateMode() { - switch (mMode.getCurrentView().getId()) { - case R.id.modeAsymmetric: { - mModeLabel.setText(R.string.label_asymmetric); - break; - } - - case R.id.modeSymmetric: { - mModeLabel.setText(R.string.label_symmetric); - break; - } - - default: { - break; - } - } - updateActionBarButtons(); - } - - private void encryptToClipboardClicked() { - mEncryptTarget = Id.target.clipboard; - initiateEncryption(); - } - - private void encryptClicked() { - Log.d(Constants.TAG, "encryptClicked invoked!"); - - if (mSource.getCurrentView().getId() == R.id.sourceFile) { - mEncryptTarget = Id.target.file; - } else { - mEncryptTarget = Id.target.email; - } - initiateEncryption(); - } - - private void initiateEncryption() { - if (mEncryptTarget == Id.target.file) { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - mInputFilename = mFilename.getText().toString(); - } - - mOutputFilename = guessOutputFilename(mInputFilename); - - if (mInputFilename.equals("")) { - AppMsg.makeText(this, R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); - return; - } - - if (!mInputFilename.startsWith("content")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - AppMsg.makeText( - this, - getString(R.string.error_message, - getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) - .show(); - return; - } - } - } - - // symmetric encryption - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - boolean gotPassPhrase = false; - String passphrase = mPassphrase.getText().toString(); - String passphraseAgain = mPassphraseAgain.getText().toString(); - if (!passphrase.equals(passphraseAgain)) { - AppMsg.makeText(this, R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); - return; - } - - gotPassPhrase = (passphrase.length() != 0); - if (!gotPassPhrase) { - AppMsg.makeText(this, R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) - .show(); - return; - } - } else { - boolean encryptIt = (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0); - // for now require at least one form of encryption for files - if (!encryptIt && mEncryptTarget == Id.target.file) { - AppMsg.makeText(this, R.string.select_encryption_key, AppMsg.STYLE_ALERT).show(); - return; - } - - if (!encryptIt && mSecretKeyId == 0) { - AppMsg.makeText(this, R.string.select_encryption_or_signature_key, - AppMsg.STYLE_ALERT).show(); - return; - } - - if (mSecretKeyId != 0 - && PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) { - showPassphraseDialog(); - - return; - } - } - - if (mEncryptTarget == Id.target.file) { - showOutputFileDialog(); - } else { - encryptStart(); - } - } - - /** - * Shows passphrase dialog to cache a new passphrase the user enters for using it later for - * encryption - */ - private void showPassphraseDialog() { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - if (mEncryptTarget == Id.target.file) { - showOutputFileDialog(); - } else { - encryptStart(); - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance( - EncryptActivity.this, messenger, mSecretKeyId); - - passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); - } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); - // send message to handler to start encryption directly - returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); - } - } - - private void showOutputFileDialog() { - // Message is received after file is selected - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - encryptStart(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - mFileDialog = FileDialogFragment.newInstance(messenger, - getString(R.string.title_encrypt_to_file), - getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null); - - mFileDialog.show(getSupportFragmentManager(), "fileDialog"); - } - - private void encryptStart() { - // Send all information needed to service to edit key in other thread - Intent intent = new Intent(this, KeychainIntentService.class); - - // fill values for this action - Bundle data = new Bundle(); - - boolean useAsciiArmor = true; - long encryptionKeyIds[] = null; - int compressionId = 0; - boolean signOnly = false; - long mSecretKeyIdToPass = 0; - - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - Log.d(Constants.TAG, "Symmetric encryption enabled!"); - String passphrase = mPassphrase.getText().toString(); - if (passphrase.length() == 0) { - passphrase = null; - } - data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase); - } else { - mSecretKeyIdToPass = mSecretKeyId; - encryptionKeyIds = mEncryptionKeyIds; - signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0); - } - - intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); - - // choose default settings, target and data bundle by target - if (mEncryptTarget == Id.target.file) { - useAsciiArmor = mAsciiArmor.isChecked(); - compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); - - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); - - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename); - - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); - - } else { - useAsciiArmor = true; - compressionId = Preferences.getPreferences(this).getDefaultMessageCompression(); - - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); - - String message = mMessage.getText().toString(); - if (signOnly) { - fixBadCharactersForGmail(message); - } - data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes()); - } - - if (mOverrideAsciiArmor) { - useAsciiArmor = mAsciiArmorDemand; - } - - data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyIdToPass); - data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); - data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, encryptionKeyIds); - data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); - data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature); - data.putBoolean(KeychainIntentService.ENCRYPT_SIGN_ONLY, signOnly); - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after encrypting is done in ApgService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, - getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard ApgHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get returned data bundle - Bundle data = message.getData(); - - String output; - switch (mEncryptTarget) { - case Id.target.clipboard: - output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING); - Log.d(Constants.TAG, "output: " + output); - ClipboardReflection.copyToClipboard(EncryptActivity.this, output); - AppMsg.makeText(EncryptActivity.this, - R.string.encryption_to_clipboard_successful, AppMsg.STYLE_INFO) - .show(); - break; - - case Id.target.email: - - output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING); - Log.d(Constants.TAG, "output: " + output); - - Intent sendIntent = new Intent(Intent.ACTION_SEND); - - // Type is set to text/plain so that encrypted messages can - // be sent with Whatsapp, Hangouts, SMS etc... - sendIntent.setType("text/plain"); - - sendIntent.putExtra(Intent.EXTRA_TEXT, output); - startActivity(Intent.createChooser(sendIntent, - getString(R.string.title_send_email))); - break; - - case Id.target.file: - AppMsg.makeText(EncryptActivity.this, R.string.encryption_successful, - AppMsg.STYLE_INFO).show(); - - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); - } - - if (mShareAfter.isChecked()) { - // Share encrypted file - Intent sendFileIntent = new Intent(Intent.ACTION_SEND); - sendFileIntent.setType("*/*"); - sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); - startActivity(Intent.createChooser(sendFileIntent, - getString(R.string.title_send_file))); - } - break; - - default: - // shouldn't happen - break; - - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - saveHandler.showProgressDialog(this); - - // start service with intent - startService(intent); - } - - /** - * Fixes bad message characters for gmail - * - * @param message - * @return - */ - private String fixBadCharactersForGmail(String message) { - // fix the message a bit, trailing spaces and newlines break stuff, - // because GMail sends as HTML and such things fuck up the - // signature, - // TODO: things like "<" and ">" also fuck up the signature - message = message.replaceAll(" +\n", "\n"); - message = message.replaceAll("\n\n+", "\n\n"); - message = message.replaceFirst("^\n+", ""); - // make sure there'll be exactly one newline at the end - message = message.replaceFirst("\n*$", "\n"); - - return message; - } - - private void initView() { - mSource = (ViewFlipper) findViewById(R.id.source); - mSourceLabel = (TextView) findViewById(R.id.sourceLabel); - mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious); - mSourceNext = (ImageView) findViewById(R.id.sourceNext); - - mSourcePrevious.setClickable(true); - mSourcePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_out)); - mSource.showPrevious(); - updateSource(); - } - }); - - mSourceNext.setClickable(true); - OnClickListener nextSourceClickListener = new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_out)); - mSource.showNext(); - updateSource(); - } - }; - mSourceNext.setOnClickListener(nextSourceClickListener); - - mSourceLabel.setClickable(true); - mSourceLabel.setOnClickListener(nextSourceClickListener); - - mMode = (ViewFlipper) findViewById(R.id.mode); - mModeLabel = (TextView) findViewById(R.id.modeLabel); - mModePrevious = (ImageView) findViewById(R.id.modePrevious); - mModeNext = (ImageView) findViewById(R.id.modeNext); - - mModePrevious.setClickable(true); - mModePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_in)); - mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_out)); - mMode.showPrevious(); - updateMode(); - } - }); - - OnClickListener nextModeClickListener = new OnClickListener() { - public void onClick(View v) { - mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_in)); - mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_out)); - mMode.showNext(); - updateMode(); - } - }; - mModeNext.setOnClickListener(nextModeClickListener); - - mModeLabel.setClickable(true); - mModeLabel.setOnClickListener(nextModeClickListener); - - mMessage = (EditText) findViewById(R.id.message); - mSelectKeysButton = (BootstrapButton) findViewById(R.id.btn_selectEncryptKeys); - mSign = (CheckBox) findViewById(R.id.sign); - mMainUserId = (TextView) findViewById(R.id.mainUserId); - mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); - - mPassphrase = (EditText) findViewById(R.id.passphrase); - mPassphraseAgain = (EditText) findViewById(R.id.passphraseAgain); - - // measure the height of the source_file view and set the message view's min height to that, - // so it fills mSource fully... bit of a hack. - View tmp = findViewById(R.id.sourceFile); - tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - int height = tmp.getMeasuredHeight(); - mMessage.setMinimumHeight(height); - - mFilename = (EditText) findViewById(R.id.filename); - mBrowse = (BootstrapButton) findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - FileHelper.openFile(EncryptActivity.this, mFilename.getText().toString(), "*/*", - Id.request.filename); - } - }); - - mAdvancedSettingsIcon = (FontAwesomeText) findViewById(R.id.advancedSettingsIcon); - mFileAdvancedSettingsContainer = (LinearLayout) findViewById(R.id.fileAdvancedSettingsContainer); - mFileAdvancedSettings = (TextView) findViewById(R.id.advancedSettings); - - LinearLayout advancedSettingsControl = (LinearLayout) findViewById(R.id.advancedSettingsControl); - advancedSettingsControl.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mFileAdvancedSettingsVisible = !mFileAdvancedSettingsVisible; - if (mFileAdvancedSettingsVisible) { - mAdvancedSettingsIcon.setIcon("fa-chevron-down"); - mFileAdvancedSettingsContainer.setVisibility(View.VISIBLE); - AlphaAnimation animation = new AlphaAnimation(0f, 1f); - animation.setDuration(mShortAnimationDuration); - mFileAdvancedSettingsContainer.startAnimation(animation); - mFileAdvancedSettings.setText(R.string.btn_encryption_advanced_settings_hide); - - } else { - mAdvancedSettingsIcon.setIcon("fa-chevron-right"); - AlphaAnimation animation = new AlphaAnimation(1f, 0f); - animation.setDuration(mShortAnimationDuration); - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - // do nothing - } - - @Override - public void onAnimationEnd(Animation animation) { - // making sure that at the end the container is completely removed from view - mFileAdvancedSettingsContainer.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - // do nothing - } - }); - mFileAdvancedSettingsContainer.startAnimation(animation); - mFileAdvancedSettings.setText(R.string.btn_encryption_advanced_settings_show); - } - } - }); - - mFileCompression = (Spinner) findViewById(R.id.fileCompression); - Choice[] choices = new Choice[]{ - new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" - + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.zip, "ZIP (" - + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.zlib, "ZLIB (" - + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.bzip2, "BZIP2 (" - + getString(R.string.compression_very_slow) + ")"), }; - ArrayAdapter adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mFileCompression.setAdapter(adapter); - - int defaultFileCompression = Preferences.getPreferences(this).getDefaultFileCompression(); - for (int i = 0; i < choices.length; ++i) { - if (choices[i].getId() == defaultFileCompression) { - mFileCompression.setSelection(i); - break; - } - } - - mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption); - mShareAfter = (CheckBox) findViewById(R.id.shareAfterEncryption); - - mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour); - mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour()); - - mSelectKeysButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - selectPublicKeys(); - } - }); - - mSign.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - CheckBox checkBox = (CheckBox) v; - if (checkBox.isChecked()) { - selectSecretKey(); - } else { - mSecretKeyId = Id.key.none; - updateView(); - } - } - }); - - mEncryptClipboard = (BootstrapButton) findViewById(R.id.action_encrypt_clipboard); - mEncryptClipboard.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - encryptToClipboardClicked(); - } - }); - mEncryptShare = (BootstrapButton) findViewById(R.id.action_encrypt_share); - mEncryptShare.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - encryptClicked(); - } - }); - mEncryptFile = (BootstrapButton) findViewById(R.id.action_encrypt_file); - mEncryptFile.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - encryptClicked(); - } - }); - } - - private void updateView() { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - mSelectKeysButton.setText(getString(R.string.select_keys_button_default)); - } else { - mSelectKeysButton.setText(getResources().getQuantityString( - R.plurals.select_keys_button, mEncryptionKeyIds.length, - mEncryptionKeyIds.length)); - } - - if (mSecretKeyId == Id.key.none) { - mSign.setChecked(false); - mMainUserId.setText(""); - mMainUserIdRest.setText(""); - } else { - String uid = getResources().getString(R.string.user_id_no_name); - String uidExtra = ""; - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, - mSecretKeyId); - if (keyRing != null) { - PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing); - if (key != null) { - String userId = PgpKeyHelper.getMainUserIdSafe(this, key); - String chunks[] = userId.split(" <", 2); - uid = chunks[0]; - if (chunks.length > 1) { - uidExtra = "<" + chunks[1]; - } - } - } - mMainUserId.setText(uid); - mMainUserIdRest.setText(uidExtra); - mSign.setChecked(true); - } - - updateActionBarButtons(); - } - - private void selectPublicKeys() { - Intent intent = new Intent(this, SelectPublicKeyActivity.class); - Vector keyIds = new Vector(); - if (mSecretKeyId != 0) { - keyIds.add(mSecretKeyId); - } - if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { - for (int i = 0; i < mEncryptionKeyIds.length; ++i) { - keyIds.add(mEncryptionKeyIds[i]); - } - } - long[] initialKeyIds = null; - if (keyIds.size() > 0) { - initialKeyIds = new long[keyIds.size()]; - for (int i = 0; i < keyIds.size(); ++i) { - initialKeyIds[i] = keyIds.get(i); - } - } - intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds); - startActivityForResult(intent, Id.request.public_keys); - } - - private void selectSecretKey() { - Intent intent = new Intent(this, SelectSecretKeyActivity.class); - startActivityForResult(intent, Id.request.secret_keys); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(this, data.getData()); - Log.d(Constants.TAG, "path=" + path); - - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); - } - } - return; - } - - case Id.request.public_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - mEncryptionKeyIds = bundle - .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS); - } - updateView(); - break; - } - - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - mSecretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID); - } else { - mSecretKeyId = Id.key.none; - } - updateView(); - break; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } - } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java new file mode 100644 index 000000000..0786b3a16 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +public interface EncryptActivityInterface { + + public boolean isModeSymmetric(); + + public long getSignatureKey(); + public long[] getEncryptionKeys(); + + public String getPassphrase(); + public String getPassphraseAgain(); + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java new file mode 100644 index 000000000..6e84211cc --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; + +import java.util.HashMap; +import java.util.Vector; + +public class EncryptAsymmetricFragment extends Fragment { + public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id"; + public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; + + public static final int RESULT_CODE_PUBLIC_KEYS = 0x00007001; + public static final int RESULT_CODE_SECRET_KEYS = 0x00007002; + + OnAsymmetricKeySelection mKeySelectionListener; + + // view + private BootstrapButton mSelectKeysButton; + private CheckBox mSign; + private TextView mMainUserId; + private TextView mMainUserIdRest; + + // model + private long mSecretKeyId = Id.key.none; + private long mEncryptionKeyIds[] = null; + + // Container Activity must implement this interface + public interface OnAsymmetricKeySelection { + public void onSigningKeySelected(long signingKeyId); + + public void onEncryptionKeysSelected(long[] encryptionKeyIds); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mKeySelectionListener = (OnAsymmetricKeySelection) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnAsymmetricKeySelection"); + } + } + + private void setSignatureKeyId(long signatureKeyId) { + mSecretKeyId = signatureKeyId; + // update key selection in EncryptActivity + mKeySelectionListener.onSigningKeySelected(signatureKeyId); + updateView(); + } + + private void setEncryptionKeyIds(long[] encryptionKeyIds) { + mEncryptionKeyIds = encryptionKeyIds; + // update key selection in EncryptActivity + mKeySelectionListener.onEncryptionKeysSelected(encryptionKeyIds); + updateView(); + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); + + mSelectKeysButton = (BootstrapButton) view.findViewById(R.id.btn_selectEncryptKeys); + mSign = (CheckBox) view.findViewById(R.id.sign); + mMainUserId = (TextView) view.findViewById(R.id.mainUserId); + mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mSelectKeysButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + selectPublicKeys(); + } + }); + mSign.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + CheckBox checkBox = (CheckBox) v; + if (checkBox.isChecked()) { + selectSecretKey(); + } else { + setSignatureKeyId(Id.key.none); + } + } + }); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + long signatureKeyId = getArguments().getLong(ARG_SIGNATURE_KEY_ID); + long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS); + + // preselect keys given by arguments (given by Intent to EncryptActivity) + preselectKeys(signatureKeyId, encryptionKeyIds); + } + + /** + * If an Intent gives a signatureKeyId and/or encryptionKeyIds, preselect those! + * + * @param preselectedSignatureKeyId + * @param preselectedEncryptionKeyIds + */ + private void preselectKeys(long preselectedSignatureKeyId, long[] preselectedEncryptionKeyIds) { + if (preselectedSignatureKeyId != 0) { + // TODO: don't use bouncy castle objects! + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(getActivity(), + preselectedSignatureKeyId); + PGPSecretKey masterKey; + if (keyRing != null) { + masterKey = keyRing.getSecretKey(); + if (masterKey != null) { + Vector signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing); + if (signKeys.size() > 0) { + setSignatureKeyId(masterKey.getKeyID()); + } + } + } + } + + if (preselectedEncryptionKeyIds != null) { + Vector goodIds = new Vector(); + for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) { + long id = ProviderHelper.getMasterKeyId(getActivity(), + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(preselectedEncryptionKeyIds[i])) + ); + // TODO check for available encrypt keys... is this even relevant? + goodIds.add(id); + } + if (goodIds.size() > 0) { + long[] keyIds = new long[goodIds.size()]; + for (int i = 0; i < goodIds.size(); ++i) { + keyIds[i] = goodIds.get(i); + } + setEncryptionKeyIds(keyIds); + } + } + } + + private void updateView() { + if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { + mSelectKeysButton.setText(getString(R.string.select_keys_button_default)); + } else { + mSelectKeysButton.setText(getResources().getQuantityString( + R.plurals.select_keys_button, mEncryptionKeyIds.length, + mEncryptionKeyIds.length)); + } + + if (mSecretKeyId == Id.key.none) { + mSign.setChecked(false); + mMainUserId.setText(""); + mMainUserIdRest.setText(""); + } else { + String uid = getResources().getString(R.string.user_id_no_name); + String uidExtra = ""; + // See if we can get a user_id from a unified query + String user_id = (String) ProviderHelper.getUnifiedData( + getActivity(), mSecretKeyId, KeyRings.USER_ID, ProviderHelper.FIELD_TYPE_STRING); + if(user_id != null) { + String chunks[] = user_id.split(" <", 2); + uid = chunks[0]; + if (chunks.length > 1) { + uidExtra = "<" + chunks[1]; + } + } + + mMainUserId.setText(uid); + mMainUserIdRest.setText(uidExtra); + mSign.setChecked(true); + } + } + + private void selectPublicKeys() { + Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class); + Vector keyIds = new Vector(); + if (mSecretKeyId != 0) { + keyIds.add(mSecretKeyId); + } + if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { + for (int i = 0; i < mEncryptionKeyIds.length; ++i) { + keyIds.add(mEncryptionKeyIds[i]); + } + } + long[] initialKeyIds = null; + if (keyIds.size() > 0) { + initialKeyIds = new long[keyIds.size()]; + for (int i = 0; i < keyIds.size(); ++i) { + initialKeyIds[i] = keyIds.get(i); + } + } + intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds); + startActivityForResult(intent, Id.request.public_keys); + } + + private void selectSecretKey() { + Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class); + startActivityForResult(intent, Id.request.secret_keys); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case RESULT_CODE_PUBLIC_KEYS: { + if (resultCode == Activity.RESULT_OK) { + Bundle bundle = data.getExtras(); + setEncryptionKeyIds(bundle + .getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS)); + } + break; + } + + case RESULT_CODE_SECRET_KEYS: { + if (resultCode == Activity.RESULT_OK) { + Uri uriMasterKey = data.getData(); + setSignatureKeyId(Long.valueOf(uriMasterKey.getLastPathSegment())); + } else { + setSignatureKeyId(Id.key.none); + } + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java new file mode 100644 index 000000000..470c85715 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; + +import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.FileHelper; +import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.util.Choice; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.File; + +public class EncryptFileFragment extends Fragment { + public static final String ARG_FILENAME = "filename"; + public static final String ARG_ASCII_ARMOR = "ascii_armor"; + + private static final int RESULT_CODE_FILE = 0x00007003; + + private EncryptActivityInterface mEncryptInterface; + + // view + private CheckBox mAsciiArmor = null; + private Spinner mFileCompression = null; + private EditText mFilename = null; + private CheckBox mDeleteAfter = null; + private CheckBox mShareAfter = null; + private BootstrapButton mBrowse = null; + private BootstrapButton mEncryptFile; + + private FileDialogFragment mFileDialog; + + // model + private String mInputFilename = null; + private String mOutputFilename = null; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mEncryptInterface = (EncryptActivityInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); + } + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_file_fragment, container, false); + + mEncryptFile = (BootstrapButton) view.findViewById(R.id.action_encrypt_file); + mEncryptFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encryptClicked(); + } + }); + + mFilename = (EditText) view.findViewById(R.id.filename); + mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); + mBrowse.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", + Id.request.filename); + } + }); + + mFileCompression = (Spinner) view.findViewById(R.id.fileCompression); + Choice[] choices = new Choice[] { + new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.zip, "ZIP (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.zlib, "ZLIB (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.bzip2, "BZIP2 (" + + getString(R.string.compression_very_slow) + ")"), + }; + ArrayAdapter adapter = new ArrayAdapter(getActivity(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mFileCompression.setAdapter(adapter); + + int defaultFileCompression = Preferences.getPreferences(getActivity()).getDefaultFileCompression(); + for (int i = 0; i < choices.length; ++i) { + if (choices[i].getId() == defaultFileCompression) { + mFileCompression.setSelection(i); + break; + } + } + + mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption); + mShareAfter = (CheckBox) view.findViewById(R.id.shareAfterEncryption); + + mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmor); + mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmor()); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + String filename = getArguments().getString(ARG_FILENAME); + if (filename != null) { + mFilename.setText(filename); + } + boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR); + if (asciiArmor) { + mAsciiArmor.setChecked(asciiArmor); + } + } + + /** + * Guess output filename based on input path + * + * @param path + * @return Suggestion for output filename + */ + private String guessOutputFilename(String path) { + // output in the same directory but with additional ending + File file = new File(path); + String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg"); + String outputFilename = file.getParent() + File.separator + file.getName() + ending; + + return outputFilename; + } + + private void showOutputFileDialog() { + // Message is received after file is selected + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == FileDialogFragment.MESSAGE_OKAY) { + Bundle data = message.getData(); + mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + encryptStart(); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + mFileDialog = FileDialogFragment.newInstance(messenger, + getString(R.string.title_encrypt_to_file), + getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null); + + mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog"); + } + + private void encryptClicked() { + String currentFilename = mFilename.getText().toString(); + if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + mInputFilename = mFilename.getText().toString(); + } + + mOutputFilename = guessOutputFilename(mInputFilename); + + if (mInputFilename.equals("")) { + AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); + return; + } + + if (!mInputFilename.startsWith("content")) { + File file = new File(mInputFilename); + if (!file.exists() || !file.isFile()) { + AppMsg.makeText( + getActivity(), + getString(R.string.error_message, + getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT) + .show(); + return; + } + } + + if (mEncryptInterface.isModeSymmetric()) { + // symmetric encryption + + boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null + && mEncryptInterface.getPassphrase().length() != 0); + if (!gotPassphrase) { + AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) + .show(); + return; + } + + if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) { + AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); + return; + } + } else { + // asymmetric encryption + + boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null + && mEncryptInterface.getEncryptionKeys().length > 0); + + if (!gotEncryptionKeys) { + AppMsg.makeText(getActivity(), R.string.select_encryption_key, AppMsg.STYLE_ALERT).show(); + return; + } + + if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) { + AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key, + AppMsg.STYLE_ALERT).show(); + return; + } + + if (mEncryptInterface.getSignatureKey() != 0 && + PassphraseCacheService.getCachedPassphrase(getActivity(), + mEncryptInterface.getSignatureKey()) == null) { + PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(), + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + showOutputFileDialog(); + } + } + }); + + return; + } + } + + showOutputFileDialog(); + } + + private void encryptStart() { + // Send all information needed to service to edit key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); + + // fill values for this action + Bundle data = new Bundle(); + + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); + + if (mEncryptInterface.isModeSymmetric()) { + Log.d(Constants.TAG, "Symmetric encryption enabled!"); + String passphrase = mEncryptInterface.getPassphrase(); + if (passphrase.length() == 0) { + passphrase = null; + } + data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase); + } else { + data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID, + mEncryptInterface.getSignatureKey()); + data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, + mEncryptInterface.getEncryptionKeys()); + } + + Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" + + mOutputFilename); + + data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + + boolean useAsciiArmor = mAsciiArmor.isChecked(); + data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); + + int compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); + data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); +// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after encrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + AppMsg.makeText(getActivity(), R.string.encryption_successful, + AppMsg.STYLE_INFO).show(); + + if (mDeleteAfter.isChecked()) { + // Create and show dialog to delete original file + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + } + + if (mShareAfter.isChecked()) { + // Share encrypted file + Intent sendFileIntent = new Intent(Intent.ACTION_SEND); + sendFileIntent.setType("*/*"); + sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); + startActivity(Intent.createChooser(sendFileIntent, + getString(R.string.title_send_file))); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case RESULT_CODE_FILE: { + if (resultCode == Activity.RESULT_OK && data != null) { + try { + String path = FileHelper.getPath(getActivity(), data.getData()); + Log.d(Constants.TAG, "path=" + path); + + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } + } + return; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java new file mode 100644 index 000000000..ba11074fc --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +public class EncryptMessageFragment extends Fragment { + public static final String ARG_TEXT = "text"; + + private EditText mMessage = null; + private BootstrapButton mEncryptShare; + private BootstrapButton mEncryptClipboard; + + private EncryptActivityInterface mEncryptInterface; + + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mEncryptInterface = (EncryptActivityInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface"); + } + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false); + + mMessage = (EditText) view.findViewById(R.id.message); + mEncryptClipboard = (BootstrapButton) view.findViewById(R.id.action_encrypt_clipboard); + mEncryptShare = (BootstrapButton) view.findViewById(R.id.action_encrypt_share); + mEncryptClipboard.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encryptClicked(true); + } + }); + mEncryptShare.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + encryptClicked(false); + } + }); + + return view; + } + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + String text = getArguments().getString(ARG_TEXT); + if (text != null) { + mMessage.setText(text); + } + } + + /** + * Fixes bad message characters for gmail + * + * @param message + * @return + */ + private String fixBadCharactersForGmail(String message) { + // fix the message a bit, trailing spaces and newlines break stuff, + // because GMail sends as HTML and such things fuck up the + // signature, + // TODO: things like "<" and ">" also fuck up the signature + message = message.replaceAll(" +\n", "\n"); + message = message.replaceAll("\n\n+", "\n\n"); + message = message.replaceFirst("^\n+", ""); + // make sure there'll be exactly one newline at the end + message = message.replaceFirst("\n*$", "\n"); + + return message; + } + + private void encryptClicked(final boolean toClipboard) { + if (mEncryptInterface.isModeSymmetric()) { + // symmetric encryption + + boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null + && mEncryptInterface.getPassphrase().length() != 0); + if (!gotPassphrase) { + AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) + .show(); + return; + } + + if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) { + AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); + return; + } + + } else { + // asymmetric encryption + + boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null + && mEncryptInterface.getEncryptionKeys().length > 0); + + if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) { + AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key, + AppMsg.STYLE_ALERT).show(); + return; + } + + if (mEncryptInterface.getSignatureKey() != 0 && + PassphraseCacheService.getCachedPassphrase(getActivity(), + mEncryptInterface.getSignatureKey()) == null) { + PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(), + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + encryptStart(toClipboard); + } + } + }); + + return; + } + } + + encryptStart(toClipboard); + } + + private void encryptStart(final boolean toClipboard) { + // Send all information needed to service to edit key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN); + + // fill values for this action + Bundle data = new Bundle(); + + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); + + String message = mMessage.getText().toString(); + + if (mEncryptInterface.isModeSymmetric()) { + Log.d(Constants.TAG, "Symmetric encryption enabled!"); + String passphrase = mEncryptInterface.getPassphrase(); + if (passphrase.length() == 0) { + passphrase = null; + } + data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase); + } else { + data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID, + mEncryptInterface.getSignatureKey()); + data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, + mEncryptInterface.getEncryptionKeys()); + + boolean signOnly = (mEncryptInterface.getEncryptionKeys() == null + || mEncryptInterface.getEncryptionKeys().length == 0); + if (signOnly) { + message = fixBadCharactersForGmail(message); + } + } + + data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes()); + + data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true); + + int compressionId = Preferences.getPreferences(getActivity()).getDefaultMessageCompression(); + data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId); +// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after encrypting is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), + getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle data = message.getData(); + + String output = new String(data.getByteArray(KeychainIntentService.RESULT_BYTES)); + Log.d(Constants.TAG, "output: " + output); + + if (toClipboard) { + ClipboardReflection.copyToClipboard(getActivity(), output); + AppMsg.makeText(getActivity(), + R.string.encryption_to_clipboard_successful, AppMsg.STYLE_INFO) + .show(); + } else { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + + // Type is set to text/plain so that encrypted messages can + // be sent with Whatsapp, Hangouts, SMS etc... + sendIntent.setType("text/plain"); + + sendIntent.putExtra(Intent.EXTRA_TEXT, output); + startActivity(Intent.createChooser(sendIntent, + getString(R.string.title_send_email))); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java new file mode 100644 index 000000000..8efa07953 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; + +public class EncryptSymmetricFragment extends Fragment { + + OnSymmetricKeySelection mPassphraseUpdateListener; + + private EditText mPassphrase; + private EditText mPassphraseAgain; + + // Container Activity must implement this interface + public interface OnSymmetricKeySelection { + public void onPassphraseUpdate(String passphrase); + + public void onPassphraseAgainUpdate(String passphrase); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mPassphraseUpdateListener = (OnSymmetricKeySelection) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnSymmetricKeySelection"); + } + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.encrypt_symmetric_fragment, container, false); + + mPassphrase = (EditText) view.findViewById(R.id.passphrase); + mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain); + mPassphrase.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + // update passphrase in EncryptActivity + mPassphraseUpdateListener.onPassphraseUpdate(s.toString()); + } + }); + mPassphraseAgain.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + // update passphrase in EncryptActivity + mPassphraseUpdateListener.onPassphraseAgainUpdate(s.toString()); + } + }); + + return view; + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 05bfc613e..9b8b92136 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2011 Senecaso - * + * * 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 @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; @@ -34,8 +35,10 @@ import android.support.v7.app.ActionBar; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; + import com.beardedhen.androidbootstrap.BootstrapButton; import com.devspark.appmsg.AppMsg; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; @@ -54,7 +57,6 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa + "IMPORT_KEY_FROM_QR_CODE"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEYSERVER"; - // TODO: implement: public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN"; @@ -72,6 +74,10 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa public static final String EXTRA_KEY_ID = "key_id"; public static final String EXTRA_FINGERPRINT = "fingerprint"; + // only used by ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN when used from OpenPgpService + public static final String EXTRA_PENDING_INTENT_DATA = "data"; + private Intent mPendingIntentData; + // view private ImportKeysListFragment mListFragment; private String[] mNavigationStrings; @@ -86,7 +92,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa ImportKeysNFCFragment.class }; - private int mCurrentNavPostition = -1; + private int mCurrentNavPosition = -1; @Override protected void onCreate(Bundle savedInstanceState) { @@ -102,17 +108,22 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa } }); - getSupportActionBar().setDisplayShowTitleEnabled(false); - - setupDrawerNavigation(savedInstanceState); - - // set drop down navigation mNavigationStrings = getResources().getStringArray(R.array.import_action_list); - Context context = getSupportActionBar().getThemedContext(); - ArrayAdapter navigationAdapter = ArrayAdapter.createFromResource(context, - R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item); - getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); - getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this); + + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { + setTitle(R.string.nav_import); + } else { + getSupportActionBar().setDisplayShowTitleEnabled(false); + + setupDrawerNavigation(savedInstanceState); + + // set drop down navigation + Context context = getSupportActionBar().getThemedContext(); + ArrayAdapter navigationAdapter = ArrayAdapter.createFromResource(context, + R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item); + getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this); + } handleActions(savedInstanceState, getIntent()); } @@ -152,33 +163,52 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa // action: directly load data startListFragment(savedInstanceState, importData, null, null); } - } else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)) { - String query = null; - if (extras.containsKey(EXTRA_QUERY)) { - query = extras.getString(EXTRA_QUERY); - } else if (extras.containsKey(EXTRA_KEY_ID)) { - long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0); - if (keyId != 0) { - query = PgpKeyHelper.convertKeyIdToHex(keyId); + } else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action) + || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action)) { + + // only used for OpenPgpService + if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) { + mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA); + } + if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) { + /* simple search based on query or key id */ + + String query = null; + if (extras.containsKey(EXTRA_QUERY)) { + query = extras.getString(EXTRA_QUERY); + } else if (extras.containsKey(EXTRA_KEY_ID)) { + long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0); + if (keyId != 0) { + query = PgpKeyHelper.convertKeyIdToHex(keyId); + } + } + + if (query != null && query.length() > 0) { + // display keyserver fragment with query + Bundle args = new Bundle(); + args.putString(ImportKeysServerFragment.ARG_QUERY, query); + loadNavFragment(0, args); + + // action: search immediately + startListFragment(savedInstanceState, null, null, query); + } else { + Log.e(Constants.TAG, "Query is empty!"); + return; } } else if (extras.containsKey(EXTRA_FINGERPRINT)) { + /* + * search based on fingerprint, here we can enforce a check in the end + * if the right key has been downloaded + */ + String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT); - if (fingerprint != null) { - query = "0x" + fingerprint; - } + loadFromFingerprint(savedInstanceState, fingerprint); } else { Log.e(Constants.TAG, - "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or 'fingerprint' extra!"); + "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " + + "'fingerprint' extra!"); return; } - - // display keyserver fragment with query - Bundle args = new Bundle(); - args.putString(ImportKeysServerFragment.ARG_QUERY, query); - loadNavFragment(0, args); - - // action: search immediately - startListFragment(savedInstanceState, null, null, query); } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) { // NOTE: this only displays the appropriate fragment, no actions are taken @@ -233,14 +263,14 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa * onNavigationItemSelected() should check whether the Fragment is already in existence * inside your Activity." *

- * from http://stackoverflow.com/questions/10983396/fragment-oncreateview-and-onactivitycreated-called-twice/14295474#14295474 + * from http://stackoverflow.com/a/14295474 *

* In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint, * the fragment would be loaded twice resulting in the query being empty after the second load. *

* Our solution: * To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment - * checks against mCurrentNavPostition. + * checks against mCurrentNavPosition. * * @param itemPosition * @param itemId @@ -256,10 +286,12 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa } private void loadNavFragment(int itemPosition, Bundle args) { - if (mCurrentNavPostition != itemPosition) { - getSupportActionBar().setSelectedNavigationItem(itemPosition); + if (mCurrentNavPosition != itemPosition) { + if (ActionBar.NAVIGATION_MODE_LIST == getSupportActionBar().getNavigationMode()) { + getSupportActionBar().setSelectedNavigationItem(itemPosition); + } loadFragment(NAVIGATION_CLASSES[itemPosition], args, mNavigationStrings[itemPosition]); - mCurrentNavPostition = itemPosition; + mCurrentNavPosition = itemPosition; } } @@ -279,7 +311,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa Log.d(Constants.TAG, "fingerprint: " + fingerprint); - if (fingerprint.length() < 16) { + loadFromFingerprint(savedInstanceState, fingerprint); + } + + public void loadFromFingerprint(Bundle savedInstanceState, String fingerprint) { + if (fingerprint == null || fingerprint.length() < 40) { AppMsg.makeText(this, R.string.import_qr_code_too_short_fingerprint, AppMsg.STYLE_ALERT).show(); return; @@ -290,6 +326,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa // display keyserver fragment with query Bundle args = new Bundle(); args.putString(ImportKeysServerFragment.ARG_QUERY, query); + args.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true); loadNavFragment(0, args); // action: search directly @@ -300,70 +337,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa mListFragment.loadNew(importData, dataUri, serverQuery, keyServer); } - // private void importAndSignOld(final long keyId, final String expectedFingerprint) { - // if (expectedFingerprint != null && expectedFingerprint.length() > 0) { - // - // Thread t = new Thread() { - // @Override - // public void run() { - // try { - // // TODO: display some sort of spinner here while the user waits - // - // // TODO: there should be only 1 - // HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]); - // String encodedKey = server.get(keyId); - // - // PGPKeyRing keyring = PGPHelper.decodeKeyRing(new ByteArrayInputStream( - // encodedKey.getBytes())); - // if (keyring != null && keyring instanceof PGPPublicKeyRing) { - // PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; - // - // // make sure the fingerprints match before we cache this thing - // String actualFingerprint = PGPHelper.convertFingerprintToHex(publicKeyRing - // .getPublicKey().getFingerprint()); - // if (expectedFingerprint.equals(actualFingerprint)) { - // // store the signed key in our local cache - // int retval = PGPMain.storeKeyRingInCache(publicKeyRing); - // if (retval != Id.return_value.ok - // && retval != Id.return_value.updated) { - // status.putString(EXTRA_ERROR, - // "Failed to store signed key in local cache"); - // } else { - // Intent intent = new Intent(ImportFromQRCodeActivity.this, - // SignKeyActivity.class); - // intent.putExtra(EXTRA_KEY_ID, keyId); - // startActivityForResult(intent, Id.request.sign_key); - // } - // } else { - // status.putString( - // EXTRA_ERROR, - // "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key."); - // } - // } - // } catch (QueryException e) { - // Log.e(TAG, "Failed to query KeyServer", e); - // status.putString(EXTRA_ERROR, "Failed to query KeyServer"); - // status.putInt(Constants.extras.STATUS, Id.message.done); - // } catch (IOException e) { - // Log.e(TAG, "Failed to query KeyServer", e); - // status.putString(EXTRA_ERROR, "Failed to query KeyServer"); - // status.putInt(Constants.extras.STATUS, Id.message.done); - // } - // } - // }; - // - // t.setName("KeyExchange Download Thread"); - // t.setDaemon(true); - // t.start(); - // } - // } - - /** * Import keys with mImportData */ public void importKeys() { - // Message is received after importing is done in ApgService + // Message is received after importing is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( this, getString(R.string.progress_importing), @@ -403,6 +381,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa BadImportKeyDialogFragment.newInstance(bad); badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog"); } + + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { + ImportKeysActivity.this.setResult(Activity.RESULT_OK, mPendingIntentData); + finish(); + } } } }; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java index 3f0b4a46e..28e2091a9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; +import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -24,9 +25,13 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import java.util.Locale; + public class ImportKeysClipboardFragment extends Fragment { private ImportKeysActivity mImportActivity; @@ -60,6 +65,10 @@ public class ImportKeysClipboardFragment extends Fragment { String sendText = ""; if (clipboardText != null) { sendText = clipboardText.toString(); + if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) { + mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText)); + return; + } } mImportActivity.loadCallback(sendText.getBytes(), null, null, null); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 9e8506193..077fa0cab 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -29,7 +29,11 @@ import com.devspark.appmsg.AppMsg; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.ui.adapter.*; +import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; +import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; +import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; +import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; +import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.KeyServer; import org.sufficientlysecure.keychain.util.Log; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java index 44b5848d8..110647284 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java @@ -57,7 +57,7 @@ public class ImportKeysNFCFragment extends Fragment { public void onClick(View v) { // show nfc help Intent intent = new Intent(getActivity(), HelpActivity.class); - intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 1); + intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 2); startActivityForResult(intent, 0); } }); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java index 10c0752b1..8b553d273 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.ui; +import com.google.zxing.integration.android.IntentResult; + import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -28,8 +30,9 @@ import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; + import com.beardedhen.androidbootstrap.BootstrapButton; -import com.google.zxing.integration.android.IntentResult; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java index 0b2fff64e..3eb463dac 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.util.Log; public class ImportKeysServerFragment extends Fragment { public static final String ARG_QUERY = "query"; public static final String ARG_KEY_SERVER = "key_server"; + public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit"; private ImportKeysActivity mImportActivity; @@ -140,6 +141,10 @@ public class ImportKeysServerFragment extends Fragment { Log.d(Constants.TAG, "keyServer: " + keyServer); } + + if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) { + mQueryEditText.setEnabled(false); + } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 078b998e1..1bc6d4ee1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -21,8 +21,8 @@ import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; + import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ExportHelper; @@ -53,27 +53,21 @@ public class KeyListActivity extends DrawerActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_key_list_import: - Intent intentImport = new Intent(this, ImportKeysActivity.class); - startActivityForResult(intentImport, 0); - + callIntentForDrawerItem(Constants.DrawerItems.IMPORT_KEYS); return true; - case R.id.menu_key_list_export: - // TODO fix this for unified keylist - mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB); - return true; case R.id.menu_key_list_create: createKey(); - return true; + case R.id.menu_key_list_create_expert: createKeyExpert(); - return true; - case R.id.menu_key_list_secret_export: - mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC); + case R.id.menu_key_list_export: + mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE_PUB, true); return true; + default: return super.onOptionsItemSelected(item); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index a08f4bc74..45db30fe5 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -24,7 +24,11 @@ import android.content.Intent; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; -import android.os.*; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -33,22 +37,29 @@ import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.SearchView; import android.text.TextUtils; -import android.view.*; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.view.animation.AnimationUtils; -import android.widget.*; import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -56,7 +67,6 @@ import se.emilsjolander.stickylistheaders.ApiLevelTooLowException; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; -import java.util.ArrayList; import java.util.HashMap; /** @@ -142,9 +152,6 @@ public class KeyListFragment extends Fragment } catch (ApiLevelTooLowException e) { } - // this view is made visible if no data is available - mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); - /* * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only * available for Android >= 3.0 @@ -178,18 +185,15 @@ public class KeyListFragment extends Fragment break; } case R.id.menu_key_list_multi_delete: { - ids = mStickyList.getWrappedList().getCheckedItemIds(); + ids = mAdapter.getCurrentSelectedMasterKeyIds(); showDeleteKeyDialog(mode, ids); break; } case R.id.menu_key_list_multi_export: { - // todo: public/secret needs to be handled differently here - ids = mStickyList.getWrappedList().getCheckedItemIds(); + ids = mAdapter.getCurrentSelectedMasterKeyIds(); ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity()); - mExportHelper - .showExportKeysDialog(ids, - Id.type.public_key, - Constants.Path.APP_DIR_FILE_PUB); + mExportHelper.showExportKeysDialog( + ids, Constants.Path.APP_DIR_FILE_PUB, mAdapter.isAnySecretSelected()); break; } case R.id.menu_key_list_multi_select_all: { @@ -243,23 +247,17 @@ public class KeyListFragment extends Fragment // These are the rows that we will retrieve. static final String[] PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.TYPE, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.UserIds.USER_ID, - KeychainContract.Keys.IS_REVOKED + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.USER_ID, + KeyRings.IS_REVOKED, + KeyRings.HAS_SECRET }; - static final int INDEX_TYPE = 1; - static final int INDEX_MASTER_KEY_ID = 2; - static final int INDEX_USER_ID = 3; - static final int INDEX_IS_REVOKED = 4; - - static final String SORT_ORDER = - // show secret before public key - KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings.TYPE + " DESC, " - // sort by user id otherwise - + UserIds.USER_ID + " ASC"; + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_USER_ID = 2; + static final int INDEX_IS_REVOKED = 3; + static final int INDEX_HAS_SECRET = 4; @Override public Loader onCreateLoader(int id, Bundle args) { @@ -269,12 +267,12 @@ public class KeyListFragment extends Fragment String where = null; String whereArgs[] = null; if (mCurQuery != null) { - where = KeychainContract.UserIds.USER_ID + " LIKE ?"; + where = KeyRings.USER_ID + " LIKE ?"; whereArgs = new String[]{"%" + mCurQuery + "%"}; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, SORT_ORDER); + return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, null); } @Override @@ -286,6 +284,9 @@ public class KeyListFragment extends Fragment mStickyList.setAdapter(mAdapter); + // this view is made visible if no data is available + mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); + // NOTE: Not supported by StickyListHeader, but reimplemented here // The list should now be shown. if (isResumed()) { @@ -315,17 +316,15 @@ public class KeyListFragment extends Fragment viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class); } viewIntent.setData( - KeychainContract - .KeyRings.buildPublicKeyRingsByMasterKeyIdUri( - Long.toString(mAdapter.getMasterKeyId(position)))); + KeyRings.buildGenericKeyRingUri(Long.toString(mAdapter.getMasterKeyId(position)))); startActivity(viewIntent); } @TargetApi(11) - protected void encrypt(ActionMode mode, long[] keyRingMasterKeyIds) { + protected void encrypt(ActionMode mode, long[] masterKeyIds) { Intent intent = new Intent(getActivity(), EncryptActivity.class); intent.setAction(EncryptActivity.ACTION_ENCRYPT); - intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingMasterKeyIds); + intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, masterKeyIds); // used instead of startActivity set actionbar based on callingPackage startActivityForResult(intent, 0); @@ -335,35 +334,17 @@ public class KeyListFragment extends Fragment /** * Show dialog to delete key * - * @param keyRingRowIds + * @param masterKeyIds */ @TargetApi(11) // TODO: this method needs an overhaul to handle both public and secret keys gracefully! - public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) { + public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds) { // Message is received after key is deleted Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { - Bundle returnData = message.getData(); - if (returnData != null - && returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) { - ArrayList notDeleted = - returnData.getStringArrayList(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED); - String notDeletedMsg = ""; - for (String userId : notDeleted) { - notDeletedMsg += userId + "\n"; - } - Toast.makeText(getActivity(), - getString(R.string.error_can_not_delete_contacts, notDeletedMsg) - + getResources() - .getQuantityString( - R.plurals.error_can_not_delete_info, - notDeleted.size()), - Toast.LENGTH_LONG).show(); - - mode.finish(); - } + mode.finish(); } } }; @@ -372,7 +353,7 @@ public class KeyListFragment extends Fragment Messenger messenger = new Messenger(returnHandler); DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - keyRingRowIds, Id.type.public_key); + masterKeyIds); deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); } @@ -506,11 +487,15 @@ public class KeyListFragment extends Fragment } { // set edit button and revoked info, specific by key type + View statusDivider = (View) view.findViewById(R.id.status_divider); + FrameLayout statusLayout = (FrameLayout) view.findViewById(R.id.status_layout); Button button = (Button) view.findViewById(R.id.edit); TextView revoked = (TextView) view.findViewById(R.id.revoked); - if (cursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) { + if (cursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) { // this is a secret key - show the edit button + statusDivider.setVisibility(View.VISIBLE); + statusLayout.setVisibility(View.VISIBLE); revoked.setVisibility(View.GONE); button.setVisibility(View.VISIBLE); @@ -518,26 +503,31 @@ public class KeyListFragment extends Fragment button.setOnClickListener(new OnClickListener() { public void onClick(View view) { Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); - editIntent.setData( - KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri( - Long.toString(id) - ) - ); + editIntent.setData(KeyRingData.buildSecretKeyRingUri(Long.toString(id))); editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); startActivityForResult(editIntent, 0); } }); } else { // this is a public key - hide the edit button, show if it's revoked + statusDivider.setVisibility(View.GONE); button.setVisibility(View.GONE); boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; + statusLayout.setVisibility(isRevoked ? View.VISIBLE : View.GONE); revoked.setVisibility(isRevoked ? View.VISIBLE : View.GONE); } } } + public boolean isSecretAvailable(int id) { + if (!mCursor.moveToPosition(id)) { + throw new IllegalStateException("couldn't move cursor to position " + id); + } + + return mCursor.getInt(INDEX_HAS_SECRET) != 0; + } public long getMasterKeyId(int id) { if (!mCursor.moveToPosition(id)) { throw new IllegalStateException("couldn't move cursor to position " + id); @@ -581,7 +571,7 @@ public class KeyListFragment extends Fragment throw new IllegalStateException("couldn't move cursor to position " + position); } - if (mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) { + if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) { { // set contact count int num = mCursor.getCount(); String contactsTotal = getResources().getQuantityString(R.plurals.n_contacts, num, num); @@ -620,7 +610,7 @@ public class KeyListFragment extends Fragment } // early breakout: all secret keys are assigned id 0 - if (mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) { + if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) { return 1L; } // otherwise, return the first character of the name as ID @@ -645,6 +635,14 @@ public class KeyListFragment extends Fragment notifyDataSetChanged(); } + public boolean isAnySecretSelected() { + for (int pos : mSelection.keySet()) { + if(mAdapter.isSecretAvailable(pos)) + return true; + } + return false; + } + public long[] getCurrentSelectedMasterKeyIds() { long[] ids = new long[mSelection.size()]; int i = 0; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java index 04179cb80..265bb2139 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -20,9 +20,15 @@ import android.annotation.SuppressLint; import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.preference.*; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceFragment; +import android.preference.PreferenceScreen; + import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; @@ -38,11 +44,11 @@ public class PreferencesActivity extends PreferenceActivity { public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV"; private PreferenceScreen mKeyServerPreference = null; - private static Preferences mPreferences; + private static Preferences sPreferences; @Override protected void onCreate(Bundle savedInstanceState) { - mPreferences = Preferences.getPreferences(this); + sPreferences = Preferences.getPreferences(this); super.onCreate(savedInstanceState); // final ActionBar actionBar = getSupportActionBar(); @@ -55,11 +61,11 @@ public class PreferencesActivity extends PreferenceActivity { if (action != null && action.equals(ACTION_PREFS_GEN)) { addPreferencesFromResource(R.xml.gen_preferences); - initializePassPassPhraceCacheTtl( - (IntegerListPreference) findPreference(Constants.Pref.PASS_PHRASE_CACHE_TTL)); + initializePassPassphraceCacheTtl( + (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS); - String servers[] = mPreferences.getKeyServers(); + String servers[] = sPreferences.getKeyServers(); mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers, servers.length, servers.length)); mKeyServerPreference @@ -68,7 +74,7 @@ public class PreferencesActivity extends PreferenceActivity { Intent intent = new Intent(PreferencesActivity.this, PreferencesKeyServerActivity.class); intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS, - mPreferences.getKeyServers()); + sPreferences.getKeyServers()); startActivityForResult(intent, Id.request.key_server_preference); return false; } @@ -104,8 +110,8 @@ public class PreferencesActivity extends PreferenceActivity { (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION), entries, values); - initializeAsciiArmour( - (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOUR)); + initializeAsciiArmor( + (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR)); initializeForceV3Signatures( (CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES)); @@ -125,7 +131,7 @@ public class PreferencesActivity extends PreferenceActivity { } String servers[] = data .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS); - mPreferences.setKeyServers(servers); + sPreferences.setKeyServers(servers); mKeyServerPreference.setSummary(getResources().getQuantityString( R.plurals.n_key_servers, servers.length, servers.length)); break; @@ -159,11 +165,11 @@ public class PreferencesActivity extends PreferenceActivity { // Load the preferences from an XML resource addPreferencesFromResource(R.xml.gen_preferences); - initializePassPassPhraceCacheTtl( - (IntegerListPreference) findPreference(Constants.Pref.PASS_PHRASE_CACHE_TTL)); + initializePassPassphraceCacheTtl( + (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS); - String servers[] = mPreferences.getKeyServers(); + String servers[] = sPreferences.getKeyServers(); mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers, servers.length, servers.length)); mKeyServerPreference @@ -172,7 +178,7 @@ public class PreferencesActivity extends PreferenceActivity { Intent intent = new Intent(getActivity(), PreferencesKeyServerActivity.class); intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS, - mPreferences.getKeyServers()); + sPreferences.getKeyServers()); startActivityForResult(intent, Id.request.key_server_preference); return false; } @@ -188,7 +194,7 @@ public class PreferencesActivity extends PreferenceActivity { } String servers[] = data .getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS); - mPreferences.setKeyServers(servers); + sPreferences.setKeyServers(servers); mKeyServerPreference.setSummary(getResources().getQuantityString( R.plurals.n_key_servers, servers.length, servers.length)); break; @@ -241,8 +247,8 @@ public class PreferencesActivity extends PreferenceActivity { (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION), entries, values); - initializeAsciiArmour( - (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOUR)); + initializeAsciiArmor( + (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR)); initializeForceV3Signatures( (CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES)); @@ -255,15 +261,15 @@ public class PreferencesActivity extends PreferenceActivity { || super.isValidFragment(fragmentName); } - private static void initializePassPassPhraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) { - mPassphraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl()); + private static void initializePassPassphraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) { + mPassphraseCacheTtl.setValue("" + sPreferences.getPassphraseCacheTtl()); mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry()); mPassphraseCacheTtl .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { mPassphraseCacheTtl.setValue(newValue.toString()); mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry()); - mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); + sPreferences.setPassphraseCacheTtl(Integer.parseInt(newValue.toString())); return false; } }); @@ -282,14 +288,14 @@ public class PreferencesActivity extends PreferenceActivity { } mEncryptionAlgorithm.setEntries(entries); mEncryptionAlgorithm.setEntryValues(values); - mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm()); + mEncryptionAlgorithm.setValue("" + sPreferences.getDefaultEncryptionAlgorithm()); mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); mEncryptionAlgorithm .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { mEncryptionAlgorithm.setValue(newValue.toString()); mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); - mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue + sPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue .toString())); return false; } @@ -309,13 +315,13 @@ public class PreferencesActivity extends PreferenceActivity { } mHashAlgorithm.setEntries(entries); mHashAlgorithm.setEntryValues(values); - mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm()); + mHashAlgorithm.setValue("" + sPreferences.getDefaultHashAlgorithm()); mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { mHashAlgorithm.setValue(newValue.toString()); mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); - mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString())); + sPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString())); return false; } }); @@ -326,14 +332,14 @@ public class PreferencesActivity extends PreferenceActivity { int[] valueIds, String[] entries, String[] values) { mMessageCompression.setEntries(entries); mMessageCompression.setEntryValues(values); - mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression()); + mMessageCompression.setValue("" + sPreferences.getDefaultMessageCompression()); mMessageCompression.setSummary(mMessageCompression.getEntry()); mMessageCompression .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { mMessageCompression.setValue(newValue.toString()); mMessageCompression.setSummary(mMessageCompression.getEntry()); - mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue + sPreferences.setDefaultMessageCompression(Integer.parseInt(newValue .toString())); return false; } @@ -344,36 +350,36 @@ public class PreferencesActivity extends PreferenceActivity { (final IntegerListPreference mFileCompression, String[] entries, String[] values) { mFileCompression.setEntries(entries); mFileCompression.setEntryValues(values); - mFileCompression.setValue("" + mPreferences.getDefaultFileCompression()); + mFileCompression.setValue("" + sPreferences.getDefaultFileCompression()); mFileCompression.setSummary(mFileCompression.getEntry()); mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { mFileCompression.setValue(newValue.toString()); mFileCompression.setSummary(mFileCompression.getEntry()); - mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString())); + sPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString())); return false; } }); } - private static void initializeAsciiArmour(final CheckBoxPreference mAsciiArmour) { - mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); - mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + private static void initializeAsciiArmor(final CheckBoxPreference mAsciiArmor) { + mAsciiArmor.setChecked(sPreferences.getDefaultAsciiArmor()); + mAsciiArmor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { - mAsciiArmour.setChecked((Boolean) newValue); - mPreferences.setDefaultAsciiArmour((Boolean) newValue); + mAsciiArmor.setChecked((Boolean) newValue); + sPreferences.setDefaultAsciiArmor((Boolean) newValue); return false; } }); } private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) { - mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures()); + mForceV3Signatures.setChecked(sPreferences.getForceV3Signatures()); mForceV3Signatures .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { mForceV3Signatures.setChecked((Boolean) newValue); - mPreferences.setForceV3Signatures((Boolean) newValue); + sPreferences.setForceV3Signatures((Boolean) newValue); return false; } }); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java index d890f35cb..719378274 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesKeyServerActivity.java @@ -91,10 +91,15 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O } } - public void onDeleted(Editor editor) { + public void onDeleted(Editor editor, boolean wasNewItem) { // nothing to do } + @Override + public void onEdited() { + + } + public void onClick(View v) { KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, mEditors, false); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java index 6ab9f1c6e..9bfe3eaa9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java @@ -32,7 +32,13 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.*; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; + import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; @@ -248,9 +254,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T @Override public Loader onCreateLoader(int id, Bundle args) { - // This is called when a new Loader needs to be created. This - // sample only has one Loader, so we don't care about the ID. - Uri baseUri = KeyRings.buildPublicKeyRingsUri(); + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); // These are the rows that we will retrieve. long now = new Date().getTime() / 1000; @@ -258,24 +262,24 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T KeyRings._ID, KeyRings.MASTER_KEY_ID, UserIds.USER_ID, - "(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS - + " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = " - + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID - + " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys." - + Keys.CAN_ENCRYPT + " = '1') AS " - + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE, - "(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS - + " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = " - + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID - + " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys." - + Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '" - + now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." - + Keys.EXPIRY + " >= '" + now + "')) AS " - + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, }; + "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k" + +" WHERE k." + Keys.MASTER_KEY_ID + " = " + + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND k." + Keys.IS_REVOKED + " = '0'" + + " AND k." + Keys.CAN_ENCRYPT + " = '1'" + + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE, + "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k" + + " WHERE k." + Keys.MASTER_KEY_ID + " = " + + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND k." + Keys.IS_REVOKED + " = '0'" + + " AND k." + Keys.CAN_ENCRYPT + " = '1'" + + " AND k." + Keys.CREATION + " <= '" + now + "'" + + " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY + " >= '" + now + "' )" + + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, }; String inMasterKeyList = null; if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { - inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN ("; + inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) { if (i != 0) { inMasterKeyList += ", "; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java index 1509bc88c..0ff88d97c 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java @@ -1,42 +1,37 @@ /* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (C) 2010 Thialfihar + * Copyright (C) 2012-2014 Dominik Schürmann * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; -import android.view.Menu; -import org.sufficientlysecure.keychain.Constants; + import org.sufficientlysecure.keychain.R; public class SelectSecretKeyActivity extends ActionBarActivity { - // Actions for internal use only: - public static final String ACTION_SELECT_SECRET_KEY = Constants.INTENT_PREFIX - + "SELECT_SECRET_KEYRING"; - public static final String EXTRA_FILTER_CERTIFY = "filter_certify"; public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id"; - public static final String RESULT_EXTRA_USER_ID = "user_id"; - private boolean mFilterCertify = false; + private boolean mFilterCertify; private SelectSecretKeyFragment mSelectFragment; @Override @@ -50,23 +45,8 @@ public class SelectSecretKeyActivity extends ActionBarActivity { actionBar.setDisplayHomeAsUpEnabled(false); actionBar.setHomeButtonEnabled(false); - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - // TODO: reimplement! - // mFilterLayout = findViewById(R.id.layout_filter); - // mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); - // mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); - // - // mClearFilterButton.setOnClickListener(new OnClickListener() { - // public void onClick(View v) { - // handleIntent(new Intent()); - // } - // }); - mFilterCertify = getIntent().getBooleanExtra(EXTRA_FILTER_CERTIFY, false); - handleIntent(getIntent()); - // Check that the activity is using the layout version with // the fragment_container FrameLayout if (findViewById(R.id.select_secret_key_fragment_container) != null) { @@ -90,48 +70,14 @@ public class SelectSecretKeyActivity extends ActionBarActivity { /** * This is executed by SelectSecretKeyFragment after clicking on an item * - * @param masterKeyId - * @param userId + * @param selectedUri */ - public void afterListSelection(long masterKeyId, String userId) { + public void afterListSelection(Uri selectedUri) { Intent data = new Intent(); - data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, masterKeyId); - data.putExtra(RESULT_EXTRA_USER_ID, (String) userId); + data.setData(selectedUri); + setResult(RESULT_OK, data); finish(); } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - // TODO: reimplement! - - // String searchString = null; - // if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - // searchString = intent.getStringExtra(SearchManager.QUERY); - // if (searchString != null && searchString.trim().length() == 0) { - // searchString = null; - // } - // } - - // if (searchString == null) { - // mFilterLayout.setVisibility(View.GONE); - // } else { - // mFilterLayout.setVisibility(View.VISIBLE); - // mFilterInfo.setText(getString(R.string.filterInfo, searchString)); - // } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // TODO: reimplement! - // menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon( - // android.R.drawable.ic_menu_search); - return true; - } - } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java index 47a3fbad3..9987facbc 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Dominik Schürmann + * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2010 Thialfihar * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; + import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -55,10 +56,9 @@ public class SelectSecretKeyFragment extends ListFragment implements */ public static SelectSecretKeyFragment newInstance(boolean filterCertify) { SelectSecretKeyFragment frag = new SelectSecretKeyFragment(); + Bundle args = new Bundle(); - args.putBoolean(ARG_FILTER_CERTIFY, filterCertify); - frag.setArguments(args); return frag; @@ -85,10 +85,10 @@ public class SelectSecretKeyFragment extends ListFragment implements @Override public void onItemClick(AdapterView adapterView, View view, int position, long id) { long masterKeyId = mAdapter.getMasterKeyId(position); - String userId = mAdapter.getUserId(position); + Uri result = KeyRings.buildGenericKeyRingUri(String.valueOf(masterKeyId)); // return data to activity, which results in finishing it - mActivity.afterListSelection(masterKeyId, userId); + mActivity.afterListSelection(result); } }); @@ -112,12 +112,7 @@ public class SelectSecretKeyFragment extends ListFragment implements public Loader onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. - Uri baseUri = KeyRings.buildSecretKeyRingsUri(); - - String capFilter = null; - if (mFilterCertify) { - capFilter = "(cert > 0)"; - } + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); // These are the rows that we will retrieve. long now = new Date().getTime() / 1000; @@ -125,29 +120,36 @@ public class SelectSecretKeyFragment extends ListFragment implements KeyRings._ID, KeyRings.MASTER_KEY_ID, UserIds.USER_ID, - "(SELECT COUNT(cert_keys." + Keys._ID + ") FROM " + Tables.KEYS - + " AS cert_keys WHERE cert_keys." + Keys.KEY_RING_ROW_ID + " = " - + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND cert_keys." - + Keys.CAN_CERTIFY + " = '1') AS cert", - "(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS - + " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = " - + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID - + " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys." - + Keys.CAN_SIGN + " = '1') AS " - + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE, - "(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS - + " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = " - + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys." - + Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_SIGN - + " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND " - + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY - + " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, }; + "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k" + + " WHERE k." + Keys.MASTER_KEY_ID + " = " + + KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + + " AND k." + Keys.CAN_CERTIFY + " = '1'" + + ") AS cert", + "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k" + +" WHERE k." + Keys.MASTER_KEY_ID + " = " + + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND k." + Keys.IS_REVOKED + " = '0'" + + " AND k." + Keys.CAN_SIGN + " = '1'" + + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE, + "(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k" + + " WHERE k." + Keys.MASTER_KEY_ID + " = " + + KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND k." + Keys.IS_REVOKED + " = '0'" + + " AND k." + Keys.CAN_SIGN + " = '1'" + + " AND k." + Keys.CREATION + " <= '" + now + "'" + + " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY + " >= '" + now + "' )" + + ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, }; String orderBy = UserIds.USER_ID + " ASC"; + String where = Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL"; + if (mFilterCertify) { + where += " AND (cert > 0)"; + } + // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, projection, capFilter, null, orderBy); + return new CursorLoader(getActivity(), baseUri, projection, where, null, orderBy); } @Override diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java index 3d22ca9f6..514951385 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java @@ -19,22 +19,26 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.TextView; + import com.beardedhen.androidbootstrap.BootstrapButton; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.sufficientlysecure.keychain.Id; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; -public class SelectSecretKeyLayoutFragment extends Fragment { +public class SelectSecretKeyLayoutFragment extends Fragment implements LoaderManager.LoaderCallbacks { private TextView mKeyUserId; private TextView mKeyUserIdRest; @@ -43,10 +47,23 @@ public class SelectSecretKeyLayoutFragment extends Fragment { private BootstrapButton mSelectKeyButton; private Boolean mFilterCertify; + private Uri mReceivedUri = null; + private SelectSecretKeyCallback mCallback; private static final int REQUEST_CODE_SELECT_KEY = 8882; + private static final int LOADER_ID = 0; + + //The Projection we will retrieve, Master Key ID is for convenience sake, + //to avoid having to pass the Key Around + final String[] PROJECTION = new String[] { + KeychainContract.Keys.MASTER_KEY_ID, + KeychainContract.UserIds.USER_ID + }; + final int INDEX_MASTER_KEY_ID = 0; + final int INDEX_USER_ID = 1; + public interface SelectSecretKeyCallback { void onKeySelected(long secretKeyId); } @@ -59,68 +76,30 @@ public class SelectSecretKeyLayoutFragment extends Fragment { mFilterCertify = filterCertify; } - public void selectKey(long secretKeyId) { - if (secretKeyId == Id.key.none) { - mNoKeySelected.setVisibility(View.VISIBLE); - mKeyUserId.setVisibility(View.GONE); - mKeyUserIdRest.setVisibility(View.GONE); - mKeyMasterKeyIdHex.setVisibility(View.GONE); + public void setNoKeySelected() { + mNoKeySelected.setVisibility(View.VISIBLE); + mKeyUserId.setVisibility(View.GONE); + mKeyUserIdRest.setVisibility(View.GONE); + mKeyMasterKeyIdHex.setVisibility(View.GONE); + } - } else { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId( - getActivity(), secretKeyId); - if (keyRing != null) { - PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing); - String masterkeyIdHex = PgpKeyHelper.convertKeyIdToHex(secretKeyId); + public void setSelectedKeyData(String userName, String email, String masterKeyHex) { - if (key != null) { - String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key); + mNoKeySelected.setVisibility(View.GONE); - String[] userIdSplit = PgpKeyHelper.splitUserId(userId); - String userName, userEmail; + mKeyUserId.setText(userName); + mKeyUserIdRest.setText(email); + mKeyMasterKeyIdHex.setText(masterKeyHex); - if (userIdSplit[0] != null) { - userName = userIdSplit[0]; - } else { - userName = getActivity().getResources().getString(R.string.user_id_no_name); - } + mKeyUserId.setVisibility(View.VISIBLE); + mKeyUserIdRest.setVisibility(View.VISIBLE); + mKeyMasterKeyIdHex.setVisibility(View.VISIBLE); - if (userIdSplit[1] != null) { - userEmail = userIdSplit[1]; - } else { - userEmail = getActivity().getResources().getString(R.string.error_user_id_no_email); - } - - mKeyMasterKeyIdHex.setText(masterkeyIdHex); - mKeyUserId.setText(userName); - mKeyUserIdRest.setText(userEmail); - mKeyMasterKeyIdHex.setVisibility(View.VISIBLE); - mKeyUserId.setVisibility(View.VISIBLE); - mKeyUserIdRest.setVisibility(View.VISIBLE); - mNoKeySelected.setVisibility(View.GONE); - } else { - mKeyMasterKeyIdHex.setVisibility(View.GONE); - mKeyUserId.setVisibility(View.GONE); - mKeyUserIdRest.setVisibility(View.GONE); - mNoKeySelected.setVisibility(View.VISIBLE); - } - } else { - mKeyMasterKeyIdHex.setText( - getActivity().getResources() - .getString(R.string.no_keys_added_or_updated) - + " for master id: " + secretKeyId); - mKeyMasterKeyIdHex.setVisibility(View.VISIBLE); - mKeyUserId.setVisibility(View.GONE); - mKeyUserIdRest.setVisibility(View.GONE); - mNoKeySelected.setVisibility(View.GONE); - } - - } } public void setError(String error) { - mKeyUserId.requestFocus(); - mKeyUserId.setError(error); + mNoKeySelected.requestFocus(); + mNoKeySelected.setError(error); } /** @@ -147,29 +126,78 @@ public class SelectSecretKeyLayoutFragment extends Fragment { return view; } + //For AppSettingsFragment + public void selectKey(long masterKeyId) { + Uri buildUri = KeychainContract.KeyRings.buildGenericKeyRingUri(String.valueOf(masterKeyId)); + mReceivedUri = buildUri; + getActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this); + } + private void startSelectKeyActivity() { Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class); intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_CERTIFY, mFilterCertify); startActivityForResult(intent, REQUEST_CODE_SELECT_KEY); } - // Select Secret Key Activity delivers the intent which was sent by it using interface to Select - // Secret Key Fragment.Intent contains Master Key Id, User Email, User Name, Master Key Id Hex. + @Override + public Loader onCreateLoader(int id, Bundle args) { + Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mReceivedUri); + //We don't care about the Loader id + return new CursorLoader(getActivity(), uri, PROJECTION, null, null, null); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + if (data.moveToFirst()) { + String userName, email, masterKeyHex; + String userID = data.getString(INDEX_USER_ID); + long masterKeyID = data.getLong(INDEX_MASTER_KEY_ID); + + String splitUserID[] = PgpKeyHelper.splitUserId(userID); + + if (splitUserID[0] != null) { + userName = splitUserID[0]; + } else { + userName = getActivity().getResources().getString(R.string.user_id_no_name); + } + + if (splitUserID[1] != null) { + email = splitUserID[1]; + } else { + email = getActivity().getResources().getString(R.string.error_user_id_no_email); + } + + //TODO Can the cursor return invalid values for the Master Key ? + masterKeyHex = PgpKeyHelper.convertKeyIdToHexShort(masterKeyID); + + //Set the data + setSelectedKeyData(userName, email, masterKeyHex); + + //Give value to the callback + mCallback.onKeySelected(masterKeyID); + } else { + //Set The empty View + setNoKeySelected(); + } + + } + + @Override + public void onLoaderReset(Loader loader) { + return; + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode & 0xFFFF) { + switch (requestCode) { case REQUEST_CODE_SELECT_KEY: { - long secretKeyId; if (resultCode == Activity.RESULT_OK) { - Bundle bundle = data.getExtras(); - secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID); - selectKey(secretKeyId); + mReceivedUri = data.getData(); + + //Must be restartLoader() or the data will not be updated on selecting a new key + getActivity().getSupportLoaderManager().restartLoader(0, null, this); - // remove displayed errors mKeyUserId.setError(null); - - // give value back to callback - mCallback.onKeySelected(secretKeyId); } break; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 2c8f66488..0e231e6a8 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -98,11 +98,11 @@ public class UploadKeyActivity extends ActionBarActivity { intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after uploading is done in ApgService + // Message is received after uploading is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index 611864d8e..b273955dd 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -37,7 +37,9 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; @@ -50,15 +52,14 @@ public class ViewCertActivity extends ActionBarActivity // These are the rows that we will retrieve. static final String[] PROJECTION = new String[] { - KeychainContract.Certs._ID, - KeychainContract.Certs.KEY_ID, - KeychainContract.UserIds.USER_ID, - KeychainContract.Certs.CREATION, - KeychainContract.Certs.KEY_ID_CERTIFIER, - "signer_uid", - KeychainContract.Certs.KEY_DATA + Certs.MASTER_KEY_ID, + Certs.USER_ID, + Certs.CREATION, + Certs.KEY_ID_CERTIFIER, + Certs.SIGNER_UID, + Certs.KEY_DATA }; - private static final int INDEX_KEY_ID = 1; + private static final int INDEX_MASTER_KEY_ID = 1; private static final int INDEX_USER_ID = 2; private static final int INDEX_CREATION = 3; private static final int INDEX_KEY_ID_CERTIFIER = 4; @@ -112,7 +113,7 @@ public class ViewCertActivity extends ActionBarActivity @Override public void onLoadFinished(Loader loader, Cursor data) { if(data.moveToFirst()) { - String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_KEY_ID)); + String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID)); mSigneeKey.setText(signeeKey); String signeeUid = data.getString(INDEX_USER_ID); @@ -179,17 +180,21 @@ public class ViewCertActivity extends ActionBarActivity return true; case R.id.menu_view_cert_view_signer: // can't do this before the data is initialized - // TODO notify user of this, maybe offer download? - if(mSignerKeyId == 0) - return true; Intent viewIntent = null; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { viewIntent = new Intent(this, ViewKeyActivity.class); } else { viewIntent = new Intent(this, ViewKeyActivityJB.class); } - viewIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri( - Long.toString(mSignerKeyId)) + // + long signerMasterKeyId = ProviderHelper.getMasterKeyId(this, + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mSignerKeyId)) + ); + // TODO notify user of this, maybe offer download? + if(mSignerKeyId == 0L) + return true; + viewIntent.setData(KeyRings.buildGenericKeyRingUri( + Long.toString(signerMasterKeyId)) ); startActivity(viewIntent); return true; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 592c4890b..7b9ba4b2d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -28,6 +29,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; +import android.view.Window; import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; @@ -38,10 +40,8 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment; -import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -60,6 +60,7 @@ public class ViewKeyActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); super.onCreate(savedInstanceState); mExportHelper = new ExportHelper(this); @@ -83,11 +84,7 @@ public class ViewKeyActivity extends ActionBarActivity { selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); } - // normalize mDataUri to a "by row id" query, to ensure it works with any - // given valid /public/ query - long rowId = ProviderHelper.getRowId(this, getIntent().getData()); - // TODO: handle (rowId == 0) with something else than a crash - mDataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)); + mDataUri = getIntent().getData(); Bundle mainBundle = new Bundle(); mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); @@ -95,7 +92,7 @@ public class ViewKeyActivity extends ActionBarActivity { ViewKeyMainFragment.class, mainBundle, (selectedTab == 0)); Bundle certBundle = new Bundle(); - certBundle.putLong(ViewKeyCertsFragment.ARG_KEYRING_ROW_ID, rowId); + certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, mDataUri); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)), ViewKeyCertsFragment.class, certBundle, (selectedTab == 1)); } @@ -122,8 +119,12 @@ public class ViewKeyActivity extends ActionBarActivity { uploadToKeyserver(mDataUri); return true; case R.id.menu_key_view_export_file: - long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())}; - mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB); + long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); + mExportHelper.showExportKeysDialog( + new long[] { masterKeyId } , Constants.Path.APP_DIR_FILE_PUB, + // TODO this doesn't work? + ((ViewKeyMainFragment) mTabsAdapter.getItem(0)).isSecretAvailable() + ); return true; case R.id.menu_key_view_share_default_fingerprint: shareKey(mDataUri, true); @@ -158,33 +159,37 @@ public class ViewKeyActivity extends ActionBarActivity { } private void updateFromKeyserver(Uri dataUri) { - long updateKeyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, dataUri); - - if (updateKeyId == 0) { - Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!"); - return; - } + byte[] blob = (byte[]) ProviderHelper.getGenericData( + this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), + KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob); Intent queryIntent = new Intent(this, ImportKeysActivity.class); - queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); - queryIntent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, updateKeyId); + queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN); + queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint); - // TODO: lookup with onactivityresult! startActivityForResult(queryIntent, RESULT_CODE_LOOKUP_KEY); } private void shareKey(Uri dataUri, boolean fingerprintOnly) { String content; if (fingerprintOnly) { - byte[] fingerprintBlob = ProviderHelper.getFingerprint(this, dataUri); - String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, false); - - content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; + byte[] data = (byte[]) ProviderHelper.getGenericData( + this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), + KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + if(data != null) { + String fingerprint = PgpKeyHelper.convertFingerprintToHex(data); + content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; + } else { + Toast.makeText(getApplicationContext(), "Bad key selected!", + Toast.LENGTH_LONG).show(); + return; + } } else { // get public keyring as ascii armored string long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri); - ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, - dataUri, new long[]{masterKeyId}); + ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString( + this, new long[]{ masterKeyId }); content = keyringArmored.get(0); @@ -214,8 +219,8 @@ public class ViewKeyActivity extends ActionBarActivity { private void copyToClipboard(Uri dataUri) { // get public keyring as ascii armored string long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri); - ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri, - new long[]{masterKeyId}); + ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString( + this, new long[]{ masterKeyId }); ClipboardReflection.copyToClipboard(this, keyringArmored.get(0)); Toast.makeText(getApplicationContext(), R.string.key_copied_to_clipboard, Toast.LENGTH_LONG) @@ -232,25 +237,29 @@ public class ViewKeyActivity extends ActionBarActivity { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { - if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { - Bundle returnData = message.getData(); - if (returnData != null - && returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) { - // we delete only this key, so MESSAGE_NOT_DELETED will solely contain this key - Toast.makeText(ViewKeyActivity.this, - getString(R.string.error_can_not_delete_contact) - + getResources() - .getQuantityString(R.plurals.error_can_not_delete_info, 1), - Toast.LENGTH_LONG).show(); - } else { - setResult(RESULT_CANCELED); - finish(); - } - } + setResult(RESULT_CANCELED); + finish(); } }; - mExportHelper.deleteKey(dataUri, Id.type.public_key, returnHandler); + mExportHelper.deleteKey(dataUri, returnHandler); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case RESULT_CODE_LOOKUP_KEY: { + if (resultCode == Activity.RESULT_OK) { + // TODO: reload key??? move this into fragment? + } + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java index 997ff9c7a..6ce7d9aa8 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java @@ -34,6 +34,9 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback, @@ -47,26 +50,18 @@ public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMess @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - initNfc(mDataUri); } /** * NFC: Initialize NFC sharing if OS and device supports it */ - private void initNfc(Uri dataUri) { + private void initNfc() { // check if NFC Beam is supported (>= Android 4.1) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { // Check for available NFC Adapter mNfcAdapter = NfcAdapter.getDefaultAdapter(this); if (mNfcAdapter != null) { // init nfc - - // get public keyring as byte array - long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri); - mSharedKeyringBytes = ProviderHelper.getKeyRingsAsByteArray(this, dataUri, - new long[]{masterKeyId}); - // Register callback to set NDEF message mNfcAdapter.setNdefPushMessageCallback(this, this); // Register callback to listen for message-sent success @@ -86,9 +81,19 @@ public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMess * guarantee that this activity starts when receiving a beamed message. For now, this code * uses the tag dispatch system. */ - NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME, - mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME)); - return msg; + // get public keyring as byte array + long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); + try { + mSharedKeyringBytes = ProviderHelper.getPGPPublicKeyRing(this, masterKeyId).getEncoded(); + + NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME, + mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME)); + return msg; + } catch(IOException e) { + // not much trouble, but leave a note + Log.e(Constants.TAG, "Error parsing keyring: ", e); + return null; + } } /** diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java index c65e9e691..26e72f10b 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java @@ -53,20 +53,21 @@ public class ViewKeyCertsFragment extends Fragment // These are the rows that we will retrieve. static final String[] PROJECTION = new String[] { KeychainContract.Certs._ID, + KeychainContract.Certs.MASTER_KEY_ID, KeychainContract.Certs.VERIFIED, KeychainContract.Certs.RANK, KeychainContract.Certs.KEY_ID_CERTIFIER, - KeychainContract.UserIds.USER_ID, - "signer_uid" + KeychainContract.Certs.USER_ID, + KeychainContract.Certs.SIGNER_UID }; // sort by our user id, static final String SORT_ORDER = KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " ASC, " + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.VERIFIED + " DESC, " - + "signer_uid ASC"; + + KeychainContract.Certs.SIGNER_UID + " ASC"; - public static final String ARG_KEYRING_ROW_ID = "row_id"; + public static final String ARG_DATA_URI = "data_uri"; private StickyListHeadersListView mStickyList; private Spinner mSpinner; @@ -125,14 +126,14 @@ public class ViewKeyCertsFragment extends Fragment mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list); - if (!getArguments().containsKey(ARG_KEYRING_ROW_ID)) { + if (!getArguments().containsKey(ARG_DATA_URI)) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } - long rowId = getArguments().getLong(ARG_KEYRING_ROW_ID); - mBaseUri = KeychainContract.Certs.buildCertsByKeyRowIdUri(Long.toString(rowId)); + Uri uri = getArguments().getParcelable(ARG_DATA_URI); + mBaseUri = KeychainContract.Certs.buildCertsUri(uri); mStickyList.setAreHeadersSticky(true); mStickyList.setDrawingListUnderStickyHeader(false); @@ -229,12 +230,12 @@ public class ViewKeyCertsFragment extends Fragment private void initIndex(Cursor cursor) { if (cursor != null) { - mIndexCertId = cursor.getColumnIndexOrThrow(KeychainContract.Certs._ID); + mIndexCertId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID); mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID); mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.RANK); mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED); mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER); - mIndexSignerUserId = cursor.getColumnIndexOrThrow("signer_uid"); + mIndexSignerUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index 8f8c13c29..b5a800712 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -30,15 +30,19 @@ import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; + import com.beardedhen.androidbootstrap.BootstrapButton; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter; @@ -52,6 +56,7 @@ public class ViewKeyMainFragment extends Fragment implements public static final String ARG_DATA_URI = "uri"; + private LinearLayout mContainer; private TextView mName; private TextView mEmail; private TextView mComment; @@ -68,7 +73,7 @@ public class ViewKeyMainFragment extends Fragment implements private ListView mUserIds; private ListView mKeys; - private static final int LOADER_ID_KEYRING = 0; + private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; private static final int LOADER_ID_KEYS = 2; @@ -77,10 +82,14 @@ public class ViewKeyMainFragment extends Fragment implements private Uri mDataUri; + // for activity + private boolean mSecretAvailable = false; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.view_key_main_fragment, container, false); + mContainer = (LinearLayout) view.findViewById(R.id.container); mName = (TextView) view.findViewById(R.id.name); mEmail = (TextView) view.findViewById(R.id.email); mComment = (TextView) view.findViewById(R.id.comment); @@ -119,64 +128,24 @@ public class ViewKeyMainFragment extends Fragment implements return; } + getActivity().setProgressBarIndeterminateVisibility(Boolean.TRUE); + mContainer.setVisibility(View.GONE); + mDataUri = dataUri; Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); - { // label whether secret key is available, and edit button if it is - final long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), mDataUri); - if (ProviderHelper.hasSecretKeyByMasterKeyId(getActivity(), masterKeyId)) { - // set this attribute. this is a LITTLE unclean, but we have the info available - // right here, so why not. - mSecretKey.setTextColor(getResources().getColor(R.color.emphasis)); - mSecretKey.setText(R.string.secret_key_yes); - - // certify button - // TODO this button MIGHT be useful if the user wants to - // certify a private key with another... - // mActionCertify.setVisibility(View.GONE); - - // edit button - mActionEdit.setVisibility(View.VISIBLE); - mActionEdit.setOnClickListener(new View.OnClickListener() { - public void onClick(View view) { - Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); - editIntent.setData( - KeychainContract - .KeyRings.buildSecretKeyRingsByMasterKeyIdUri( - Long.toString(masterKeyId))); - editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); - startActivityForResult(editIntent, 0); - } - }); - } else { - mSecretKey.setTextColor(Color.BLACK); - mSecretKey.setText(getResources().getString(R.string.secret_key_no)); - - // certify button - mActionCertify.setVisibility(View.VISIBLE); - // edit button - mActionEdit.setVisibility(View.GONE); - } - - // TODO see todo note above, doing this here for now - mActionCertify.setOnClickListener(new View.OnClickListener() { - public void onClick(View view) { - certifyKey(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri( - Long.toString(masterKeyId) - )); - } - }); - - } - mActionEncrypt.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { encryptToContact(mDataUri); } }); + mActionCertify.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + certifyKey(mDataUri); + } + }); mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0); mUserIds.setAdapter(mUserIdsAdapter); @@ -186,74 +155,51 @@ public class ViewKeyMainFragment extends Fragment implements // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. - getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this); + getActivity().getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this); } - static final String[] KEYRING_PROJECTION = - new String[]{KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.UserIds.USER_ID}; - static final int KEYRING_INDEX_ID = 0; - static final int KEYRING_INDEX_MASTER_KEY_ID = 1; - static final int KEYRING_INDEX_USER_ID = 2; + static final String[] UNIFIED_PROJECTION = new String[] { + KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET, + KeyRings.USER_ID, KeyRings.FINGERPRINT, + KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY, - static final String[] USER_IDS_PROJECTION = new String[]{ - KeychainContract.UserIds._ID, - KeychainContract.UserIds.USER_ID, - KeychainContract.UserIds.RANK, - "verified", }; - // not the main user id - static final String USER_IDS_SELECTION = - KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " > 0 "; - static final String USER_IDS_SORT_ORDER = - KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " COLLATE LOCALIZED ASC"; + static final int INDEX_UNIFIED_MKI = 1; + static final int INDEX_UNIFIED_HAS_SECRET = 2; + static final int INDEX_UNIFIED_UID = 3; + static final int INDEX_UNIFIED_FINGERPRINT = 4; + static final int INDEX_UNIFIED_ALGORITHM = 5; + static final int INDEX_UNIFIED_KEY_SIZE = 6; + static final int INDEX_UNIFIED_CREATION = 7; + static final int INDEX_UNIFIED_EXPIRY = 8; - static final String[] KEYS_PROJECTION = new String[]{ - KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID, - KeychainContract.Keys.IS_MASTER_KEY, KeychainContract.Keys.ALGORITHM, - KeychainContract.Keys.KEY_SIZE, KeychainContract.Keys.CAN_CERTIFY, - KeychainContract.Keys.CAN_SIGN, KeychainContract.Keys.CAN_ENCRYPT, - KeychainContract.Keys.CREATION, KeychainContract.Keys.EXPIRY, - KeychainContract.Keys.FINGERPRINT - }; - static final String KEYS_SORT_ORDER = KeychainContract.Keys.RANK + " ASC"; - static final int KEYS_INDEX_ID = 0; - static final int KEYS_INDEX_KEY_ID = 1; - static final int KEYS_INDEX_IS_MASTER_KEY = 2; - static final int KEYS_INDEX_ALGORITHM = 3; - static final int KEYS_INDEX_KEY_SIZE = 4; - static final int KEYS_INDEX_CAN_CERTIFY = 5; - static final int KEYS_INDEX_CAN_SIGN = 6; - static final int KEYS_INDEX_CAN_ENCRYPT = 7; - static final int KEYS_INDEX_CREATION = 8; - static final int KEYS_INDEX_EXPIRY = 9; - static final int KEYS_INDEX_FINGERPRINT = 10; + static final String[] USER_IDS_PROJECTION = new String[] { + UserIds._ID, UserIds.USER_ID, UserIds.RANK, + }; + + static final String[] KEYS_PROJECTION = new String[] { + Keys._ID, + Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, + Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED, + Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT + }; + static final int KEYS_INDEX_CAN_ENCRYPT = 6; public Loader onCreateLoader(int id, Bundle args) { switch (id) { - case LOADER_ID_KEYRING: { - Uri baseUri = mDataUri; - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, KEYRING_PROJECTION, null, null, null); + case LOADER_ID_UNIFIED: { + Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } case LOADER_ID_USER_IDS: { - Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null, - USER_IDS_SORT_ORDER); + Uri baseUri = UserIds.buildUserIdsUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null, null); } case LOADER_ID_KEYS: { - Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri); - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER); + Uri baseUri = Keys.buildKeysUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null); } default: @@ -262,14 +208,19 @@ public class ViewKeyMainFragment extends Fragment implements } public void onLoadFinished(Loader loader, Cursor data) { + /* TODO better error handling? May cause problems when a key is deleted, + * because the notification triggers faster than the activity closes. + */ + // Avoid NullPointerExceptions... + if(data.getCount() == 0) + return; // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { - case LOADER_ID_KEYRING: + case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { // get name, email, and comment from USER_ID - String[] mainUserId = PgpKeyHelper.splitUserId(data - .getString(KEYRING_INDEX_USER_ID)); + String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_UID)); if (mainUserId[0] != null) { getActivity().setTitle(mainUserId[0]); mName.setText(mainUserId[0]); @@ -279,63 +230,97 @@ public class ViewKeyMainFragment extends Fragment implements } mEmail.setText(mainUserId[1]); mComment.setText(mainUserId[2]); - } - break; - case LOADER_ID_USER_IDS: - mUserIdsAdapter.swapCursor(data); - break; - case LOADER_ID_KEYS: - // the first key here is our master key - if (data.moveToFirst()) { + if (data.getInt(INDEX_UNIFIED_HAS_SECRET) != 0) { + mSecretAvailable = true; + + mSecretKey.setTextColor(getResources().getColor(R.color.emphasis)); + mSecretKey.setText(R.string.secret_key_yes); + + // edit button + mActionEdit.setVisibility(View.VISIBLE); + mActionEdit.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + Intent editIntent = new Intent(getActivity(), EditKeyActivity.class); + editIntent.setData(mDataUri); + editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); + startActivityForResult(editIntent, 0); + } + }); + } else { + mSecretAvailable = false; + + mSecretKey.setTextColor(Color.BLACK); + mSecretKey.setText(getResources().getString(R.string.secret_key_no)); + + // certify button + mActionCertify.setVisibility(View.VISIBLE); + // edit button + mActionEdit.setVisibility(View.GONE); + } + // get key id from MASTER_KEY_ID - long keyId = data.getLong(KEYS_INDEX_KEY_ID); - - String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId); + long masterKeyId = data.getLong(INDEX_UNIFIED_MKI); + String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId); mKeyId.setText(keyIdStr); // get creation date from CREATION - if (data.isNull(KEYS_INDEX_CREATION)) { + if (data.isNull(INDEX_UNIFIED_CREATION)) { mCreation.setText(R.string.none); } else { - Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000); + Date creationDate = new Date(data.getLong(INDEX_UNIFIED_CREATION) * 1000); mCreation.setText( DateFormat.getDateFormat(getActivity().getApplicationContext()).format( - creationDate)); + creationDate)); } // get expiry date from EXPIRY - if (data.isNull(KEYS_INDEX_EXPIRY)) { + if (data.isNull(INDEX_UNIFIED_EXPIRY)) { mExpiry.setText(R.string.none); } else { - Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000); + Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000); mExpiry.setText( DateFormat.getDateFormat(getActivity().getApplicationContext()).format( - expiryDate)); + expiryDate)); } String algorithmStr = PgpKeyHelper.getAlgorithmInfo( - data.getInt(KEYS_INDEX_ALGORITHM), data.getInt(KEYS_INDEX_KEY_SIZE)); + data.getInt(INDEX_UNIFIED_ALGORITHM), data.getInt(INDEX_UNIFIED_KEY_SIZE)); mAlgorithm.setText(algorithmStr); - byte[] fingerprintBlob = data.getBlob(KEYS_INDEX_FINGERPRINT); - if (fingerprintBlob == null) { - // FALLBACK for old database entries - fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri); - } - String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); + byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); + String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob); + mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint)); - mFingerprint.setText(OtherHelper.colorizeFingerprint(fingerprint)); + break; + } + } + + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(data); + break; + + case LOADER_ID_KEYS: + // hide encrypt button if no encryption key is available + boolean canEncrypt = false; + data.moveToFirst(); + do { + if (data.getInt(KEYS_INDEX_CAN_ENCRYPT) == 1) { + canEncrypt = true; + break; + } + } while (data.moveToNext()); + if (!canEncrypt) { + mActionEncrypt.setVisibility(View.GONE); } mKeysAdapter.swapCursor(data); break; - - default: - break; } + getActivity().setProgressBarIndeterminateVisibility(Boolean.FALSE); + mContainer.setVisibility(View.VISIBLE); } /** @@ -344,24 +329,25 @@ public class ViewKeyMainFragment extends Fragment implements */ public void onLoaderReset(Loader loader) { switch (loader.getId()) { - case LOADER_ID_KEYRING: - // No resources need to be freed for this ID - break; case LOADER_ID_USER_IDS: mUserIdsAdapter.swapCursor(null); break; case LOADER_ID_KEYS: mKeysAdapter.swapCursor(null); break; - default: - break; } } + /** Returns true if the key current displayed is known to have a secret key. */ + public boolean isSecretAvailable() { + return mSecretAvailable; + } + private void encryptToContact(Uri dataUri) { + // TODO preselect from uri? should be feasible without trivial query long keyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri); - long[] encryptionKeyIds = new long[]{keyId}; + long[] encryptionKeyIds = new long[]{ keyId }; Intent intent = new Intent(getActivity(), EncryptActivity.class); intent.setAction(EncryptActivity.ACTION_ENCRYPT); intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 0f05af447..f322ea980 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -30,6 +30,7 @@ import android.widget.CheckBox; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.TextView; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; @@ -43,13 +44,12 @@ public class ImportKeysAdapter extends ArrayAdapter { protected List mData; static class ViewHolder { - private TextView mMainUserId; - private TextView mMainUserIdRest; - private TextView mKeyId; - private TextView mFingerprint; - private TextView mAlgorithm; - private TextView mStatus; - + public TextView mainUserId; + public TextView mainUserIdRest; + public TextView keyId; + public TextView fingerprint; + public TextView algorithm; + public TextView status; } public ImportKeysAdapter(Activity activity) { @@ -100,12 +100,12 @@ public class ImportKeysAdapter extends ArrayAdapter { if (convertView == null) { holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.import_keys_list_entry, null); - holder.mMainUserId = (TextView) convertView.findViewById(R.id.mainUserId); - holder.mMainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest); - holder.mKeyId = (TextView) convertView.findViewById(R.id.keyId); - holder.mFingerprint = (TextView) convertView.findViewById(R.id.fingerprint); - holder.mAlgorithm = (TextView) convertView.findViewById(R.id.algorithm); - holder.mStatus = (TextView) convertView.findViewById(R.id.status); + holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId); + holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest); + holder.keyId = (TextView) convertView.findViewById(R.id.keyId); + holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint); + holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm); + holder.status = (TextView) convertView.findViewById(R.id.status); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); @@ -119,36 +119,36 @@ public class ImportKeysAdapter extends ArrayAdapter { // show red user id if it is a secret key if (entry.secretKey) { userIdSplit[0] = mActivity.getString(R.string.secret_key) + " " + userIdSplit[0]; - holder.mMainUserId.setTextColor(Color.RED); + holder.mainUserId.setTextColor(Color.RED); } - holder.mMainUserId.setText(userIdSplit[0]); + holder.mainUserId.setText(userIdSplit[0]); } else { - holder.mMainUserId.setText(R.string.user_id_no_name); + holder.mainUserId.setText(R.string.user_id_no_name); } // email if (userIdSplit[1] != null) { - holder.mMainUserIdRest.setText(userIdSplit[1]); - holder.mMainUserIdRest.setVisibility(View.VISIBLE); + holder.mainUserIdRest.setText(userIdSplit[1]); + holder.mainUserIdRest.setVisibility(View.VISIBLE); } else { - holder.mMainUserIdRest.setVisibility(View.GONE); + holder.mainUserIdRest.setVisibility(View.GONE); } - holder.mKeyId.setText(entry.hexKeyId); + holder.keyId.setText(entry.keyIdHex); - if (entry.fingerPrint != null) { - holder.mFingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint); - holder.mFingerprint.setVisibility(View.VISIBLE); + if (entry.fingerPrintHex != null) { + holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerPrintHex)); + holder.fingerprint.setVisibility(View.VISIBLE); } else { - holder.mFingerprint.setVisibility(View.GONE); + holder.fingerprint.setVisibility(View.GONE); } - holder.mAlgorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); + holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); if (entry.revoked) { - holder.mStatus.setText(R.string.revoked); + holder.status.setText(R.string.revoked); } else { - holder.mStatus.setVisibility(View.GONE); + holder.status.setVisibility(View.GONE); } LinearLayout ll = (LinearLayout) convertView.findViewById(R.id.list); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java index 19f0d1eaf..5631d40ea 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java @@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.os.Parcel; import android.os.Parcelable; +import android.util.SparseArray; + import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKeyRing; @@ -34,13 +36,13 @@ import java.util.Date; public class ImportKeysListEntry implements Serializable, Parcelable { private static final long serialVersionUID = -7797972103284992662L; - public ArrayList userIds; + public ArrayList userIds; public long keyId; + public String keyIdHex; public boolean revoked; public Date date; // TODO: not displayed - public String fingerPrint; - public String hexKeyId; + public String fingerPrintHex; public int bitStrength; public String algorithm; public boolean secretKey; @@ -54,8 +56,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.keyId = b.keyId; this.revoked = b.revoked; this.date = b.date; - this.fingerPrint = b.fingerPrint; - this.hexKeyId = b.hexKeyId; + this.fingerPrintHex = b.fingerPrintHex; + this.keyIdHex = b.keyIdHex; this.bitStrength = b.bitStrength; this.algorithm = b.algorithm; this.secretKey = b.secretKey; @@ -73,8 +75,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { dest.writeLong(keyId); dest.writeByte((byte) (revoked ? 1 : 0)); dest.writeSerializable(date); - dest.writeString(fingerPrint); - dest.writeString(hexKeyId); + dest.writeString(fingerPrintHex); + dest.writeString(keyIdHex); dest.writeInt(bitStrength); dest.writeString(algorithm); dest.writeByte((byte) (secretKey ? 1 : 0)); @@ -91,8 +93,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { vr.keyId = source.readLong(); vr.revoked = source.readByte() == 1; vr.date = (Date) source.readSerializable(); - vr.fingerPrint = source.readString(); - vr.hexKeyId = source.readString(); + vr.fingerPrintHex = source.readString(); + vr.keyIdHex = source.readString(); vr.bitStrength = source.readInt(); vr.algorithm = source.readString(); vr.secretKey = source.readByte() == 1; @@ -108,8 +110,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { } }; - public long getKeyId() { - return keyId; + public String getKeyIdHex() { + return keyIdHex; } public byte[] getBytes() { @@ -120,6 +122,82 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.mBytes = bytes; } + public boolean isSelected() { + return mSelected; + } + + public void setSelected(boolean selected) { + this.mSelected = selected; + } + + public long getKeyId() { + return keyId; + } + + public void setKeyId(long keyId) { + this.keyId = keyId; + } + + public void setKeyIdHex(String keyIdHex) { + this.keyIdHex = keyIdHex; + } + + public boolean isRevoked() { + return revoked; + } + + public void setRevoked(boolean revoked) { + this.revoked = revoked; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getFingerPrintHex() { + return fingerPrintHex; + } + + public void setFingerPrintHex(String fingerPrintHex) { + this.fingerPrintHex = fingerPrintHex; + } + + public int getBitStrength() { + return bitStrength; + } + + public void setBitStrength(int bitStrength) { + this.bitStrength = bitStrength; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public boolean isSecretKey() { + return secretKey; + } + + public void setSecretKey(boolean secretKey) { + this.secretKey = secretKey; + } + + public ArrayList getUserIds() { + return userIds; + } + + public void setUserIds(ArrayList userIds) { + this.userIds = userIds; + } + /** * Constructor for later querying from keyserver */ @@ -131,14 +209,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable { userIds = new ArrayList(); } - public boolean isSelected() { - return mSelected; - } - - public void setSelected(boolean selected) { - this.mSelected = selected; - } - /** * Constructor based on key object, used for import from NFC, QR Codes, files */ @@ -164,27 +234,41 @@ public class ImportKeysListEntry implements Serializable, Parcelable { for (String userId : new IterableIterator(pgpKeyRing.getPublicKey().getUserIDs())) { userIds.add(userId); } + this.keyId = pgpKeyRing.getPublicKey().getKeyID(); + this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId); this.revoked = pgpKeyRing.getPublicKey().isRevoked(); - this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey() - .getFingerprint(), true); - this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId); + this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey() + .getFingerprint()); this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength(); - int algorithm = pgpKeyRing.getPublicKey().getAlgorithm(); - if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL - || algorithm == PGPPublicKey.RSA_SIGN) { - this.algorithm = "RSA"; - } else if (algorithm == PGPPublicKey.DSA) { - this.algorithm = "DSA"; - } else if (algorithm == PGPPublicKey.ELGAMAL_ENCRYPT - || algorithm == PGPPublicKey.ELGAMAL_GENERAL) { - this.algorithm = "ElGamal"; - } else if (algorithm == PGPPublicKey.EC || algorithm == PGPPublicKey.ECDSA) { - this.algorithm = "ECC"; - } else { - // TODO: with resources - this.algorithm = "unknown"; - } + final int algorithm = pgpKeyRing.getPublicKey().getAlgorithm(); + this.algorithm = getAlgorithmFromId(algorithm); + } + + /** + * Based on OpenPGP Message Format + */ + private static final SparseArray ALGORITHM_IDS = new SparseArray() {{ + put(-1, "unknown"); // TODO: with resources + put(0, "unencrypted"); + put(PGPPublicKey.RSA_GENERAL, "RSA"); + put(PGPPublicKey.RSA_ENCRYPT, "RSA"); + put(PGPPublicKey.RSA_SIGN, "RSA"); + put(PGPPublicKey.ELGAMAL_ENCRYPT, "ElGamal"); + put(PGPPublicKey.ELGAMAL_GENERAL, "ElGamal"); + put(PGPPublicKey.DSA, "DSA"); + put(PGPPublicKey.EC, "ECC"); + put(PGPPublicKey.ECDSA, "ECC"); + put(PGPPublicKey.ECDH, "ECC"); + }}; + + /** + * Based on OpenPGP Message Format + */ + public static String getAlgorithmFromId(int algorithmId) { + return (ALGORITHM_IDS.get(algorithmId) != null ? + ALGORITHM_IDS.get(algorithmId) : + ALGORITHM_IDS.get(-1)); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java index a4dd06e40..259e14319 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.support.v4.content.AsyncTaskLoader; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.HkpKeyServer; import org.sufficientlysecure.keychain.util.KeyServer; @@ -53,7 +54,12 @@ public class ImportKeysListServerLoader return mEntryListWrapper; } - queryServer(mServerQuery, mKeyServer); + if (mServerQuery.startsWith("0x") && mServerQuery.length() == 42) { + Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!"); + queryServer(mServerQuery, mKeyServer, true); + } else { + queryServer(mServerQuery, mKeyServer, false); + } return mEntryListWrapper; } @@ -84,14 +90,30 @@ public class ImportKeysListServerLoader /** * Query keyserver */ - private void queryServer(String query, String keyServer) { + private void queryServer(String query, String keyServer, boolean enforceFingerprint) { HkpKeyServer server = new HkpKeyServer(keyServer); try { ArrayList searchResult = server.search(query); mEntryList.clear(); // add result to data - mEntryList.addAll(searchResult); + if (enforceFingerprint) { + String fingerprint = query.substring(2); + Log.d(Constants.TAG, "fingerprint: " + fingerprint); + // query must return only one result! + if (searchResult.size() > 0) { + ImportKeysListEntry uniqueEntry = searchResult.get(0); + /* + * set fingerprint explicitly after query + * to enforce a check when the key is imported by KeychainIntentService + */ + uniqueEntry.setFingerPrintHex(fingerprint); + uniqueEntry.setSelected(true); + mEntryList.add(uniqueEntry); + } + } else { + mEntryList.addAll(searchResult); + } mEntryListWrapper = new AsyncTaskResultWrapper>(mEntryList, null); } catch (KeyServer.InsufficientQuery e) { Log.e(Constants.TAG, "InsufficientQuery", e); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java index c997599bd..5b5d316b6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyValueSpinnerAdapter.java @@ -20,7 +20,11 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; import android.widget.ArrayAdapter; -import java.util.*; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; public class KeyValueSpinnerAdapter extends ArrayAdapter { private final HashMap mData; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java new file mode 100644 index 000000000..fd864eb09 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/PagerTabStripAdapter.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v7.app.ActionBarActivity; + +import java.util.ArrayList; + +public class PagerTabStripAdapter extends FragmentPagerAdapter { + private final Context mContext; + private final ArrayList mTabs = new ArrayList(); + + static final class TabInfo { + public final Class clss; + public final Bundle args; + public final String title; + + TabInfo(Class clss, Bundle args, String title) { + this.clss = clss; + this.args = args; + this.title = title; + } + } + + public PagerTabStripAdapter(ActionBarActivity activity) { + super(activity.getSupportFragmentManager()); + mContext = activity; + } + + public void addTab(Class clss, Bundle args, String title) { + TabInfo info = new TabInfo(clss, args, title); + mTabs.add(info); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mTabs.size(); + } + + @Override + public Fragment getItem(int position) { + TabInfo info = mTabs.get(position); + return Fragment.instantiate(mContext, info.clss.getName(), info.args); + } + + @Override + public CharSequence getPageTitle(int position) { + return mTabs.get(position).title; + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index beb76fc10..fbbb9caa4 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -115,7 +115,7 @@ public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter { // TODO: needed to key id to no? keyId.setText(R.string.no_key); long masterKeyId = cursor.getLong(mIndexMasterKeyId); - keyId.setText(PgpKeyHelper.convertKeyIdToHex(masterKeyId)); + keyId.setText(PgpKeyHelper.convertKeyIdToHexShort(masterKeyId)); // TODO: needed to set unknown_status? status.setText(R.string.unknown_status); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java index f435d46ef..9ddfa90be 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java @@ -36,12 +36,12 @@ public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar. private final ArrayList mTabs = new ArrayList(); static final class TabInfo { - private final Class mClss; - private final Bundle mArgs; + public final Class clss; + public final Bundle args; - TabInfo(Class mClss, Bundle mArgs) { - this.mClss = mClss; - this.mArgs = mArgs; + TabInfo(Class clss, Bundle args) { + this.clss = clss; + this.args = args; } } @@ -71,7 +71,7 @@ public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar. @Override public Fragment getItem(int position) { TabInfo info = mTabs.get(position); - return Fragment.instantiate(mContext, info.mClss.getName(), info.mArgs); + return Fragment.instantiate(mContext, info.clss.getName(), info.args); } public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java index 153a3f266..64b735bfa 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java @@ -18,27 +18,36 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; +import android.content.res.ColorStateList; import android.database.Cursor; import android.support.v4.widget.CursorAdapter; +import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import java.util.Date; + public class ViewKeyKeysAdapter extends CursorAdapter { private LayoutInflater mInflater; private int mIndexKeyId; private int mIndexAlgorithm; private int mIndexKeySize; - private int mIndexIsMasterKey; + private int mIndexRank; private int mIndexCanCertify; private int mIndexCanEncrypt; private int mIndexCanSign; + private int mIndexRevokedKey; + private int mIndexExpiry; + + private ColorStateList mDefaultTextColor; public ViewKeyKeysAdapter(Context context, Cursor c, int flags) { super(context, c, flags); @@ -66,10 +75,12 @@ public class ViewKeyKeysAdapter extends CursorAdapter { mIndexKeyId = cursor.getColumnIndexOrThrow(Keys.KEY_ID); mIndexAlgorithm = cursor.getColumnIndexOrThrow(Keys.ALGORITHM); mIndexKeySize = cursor.getColumnIndexOrThrow(Keys.KEY_SIZE); - mIndexIsMasterKey = cursor.getColumnIndexOrThrow(Keys.IS_MASTER_KEY); + mIndexRank = cursor.getColumnIndexOrThrow(Keys.RANK); mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY); mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT); mIndexCanSign = cursor.getColumnIndexOrThrow(Keys.CAN_SIGN); + mIndexRevokedKey = cursor.getColumnIndexOrThrow(Keys.IS_REVOKED); + mIndexExpiry = cursor.getColumnIndexOrThrow(Keys.EXPIRY); } } @@ -77,20 +88,21 @@ public class ViewKeyKeysAdapter extends CursorAdapter { public void bindView(View view, Context context, Cursor cursor) { TextView keyId = (TextView) view.findViewById(R.id.keyId); TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); + TextView keyExpiry = (TextView) view.findViewById(R.id.keyExpiry); ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey); ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey); ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); + ImageView revokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey); - String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)); + String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(mIndexKeyId)); String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm), cursor.getInt(mIndexKeySize)); keyId.setText(keyIdStr); - keyDetails.setText("(" + algorithmStr + ")"); - if (cursor.getInt(mIndexIsMasterKey) != 1) { + if (cursor.getInt(mIndexRank) == 0) { masterKeyIcon.setVisibility(View.INVISIBLE); } else { masterKeyIcon.setVisibility(View.VISIBLE); @@ -113,11 +125,51 @@ public class ViewKeyKeysAdapter extends CursorAdapter { } else { signIcon.setVisibility(View.VISIBLE); } + + boolean valid = true; + if (cursor.getInt(mIndexRevokedKey) > 0) { + revokedKeyIcon.setVisibility(View.VISIBLE); + + valid = false; + } else { + keyId.setTextColor(mDefaultTextColor); + keyDetails.setTextColor(mDefaultTextColor); + keyExpiry.setTextColor(mDefaultTextColor); + + revokedKeyIcon.setVisibility(View.GONE); + } + + if (!cursor.isNull(mIndexExpiry)) { + Date expiryDate = new Date(cursor.getLong(mIndexExpiry) * 1000); + + valid = valid && expiryDate.after(new Date()); + keyExpiry.setText("(" + + context.getString(R.string.label_expiry) + ": " + + DateFormat.getDateFormat(context).format(expiryDate) + ")"); + + keyExpiry.setVisibility(View.VISIBLE); + } else { + keyExpiry.setVisibility(View.GONE); + } + // if key is expired or revoked, strike through text + if (!valid) { + keyId.setText(OtherHelper.strikeOutText(keyId.getText())); + keyDetails.setText(OtherHelper.strikeOutText(keyDetails.getText())); + keyExpiry.setText(OtherHelper.strikeOutText(keyExpiry.getText())); + } + keyId.setEnabled(valid); + keyDetails.setEnabled(valid); + keyExpiry.setEnabled(valid); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.view_key_keys_item, null); + View view = mInflater.inflate(R.layout.view_key_keys_item, null); + if (mDefaultTextColor == null) { + TextView keyId = (TextView) view.findViewById(R.id.keyId); + mDefaultTextColor = keyId.getTextColors(); + } + return view; } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java index 2778ed08c..2677a1a1a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java @@ -39,7 +39,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter { private int mIndexUserId, mIndexRank; private int mVerifiedId; - final private ArrayList mCheckStates; + private final ArrayList mCheckStates; public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) { super(context, c, flags); @@ -57,9 +57,9 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter { @Override public Cursor swapCursor(Cursor newCursor) { initIndex(newCursor); - if(mCheckStates != null) { + if (mCheckStates != null) { mCheckStates.clear(); - if(newCursor != null) { + if (newCursor != null) { int count = newCursor.getCount(); mCheckStates.ensureCapacity(count); // initialize to true (use case knowledge: we usually want to sign all uids) @@ -84,7 +84,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter { if (cursor != null) { mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID); mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK); - mVerifiedId = cursor.getColumnIndexOrThrow("verified"); + // mVerifiedId = cursor.getColumnIndexOrThrow(UserIds.VERIFIED); } } @@ -106,7 +106,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter { } vAddress.setText(userId[1]); - int verified = cursor.getInt(mVerifiedId); + int verified = 1; // cursor.getInt(mVerifiedId); // TODO introduce own resource for this :) if(verified > 0) vVerified.setImageResource(android.R.drawable.presence_online); @@ -114,8 +114,9 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter { vVerified.setImageResource(android.R.drawable.presence_invisible); // don't care further if checkboxes aren't shown - if(mCheckStates == null) + if (mCheckStates == null) { return; + } final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox); final int position = cursor.getPosition(); @@ -138,8 +139,8 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter { public ArrayList getSelectedUserIds() { ArrayList result = new ArrayList(); - for(int i = 0; i < mCheckStates.size(); i++) { - if(mCheckStates.get(i)) { + for (int i = 0; i < mCheckStates.size(); i++) { + if (mCheckStates.get(i)) { mCursor.moveToPosition(i); result.add(mCursor.getString(mIndexUserId)); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java index a41bc2bee..ad558a81e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CreateKeyDialogFragment.java @@ -25,6 +25,7 @@ import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.View; +import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Spinner; import org.sufficientlysecure.keychain.Id; @@ -113,21 +114,8 @@ public class CreateKeyDialogFragment extends DialogFragment { public void onClick(DialogInterface di, int id) { di.dismiss(); try { - int nKeyIndex = keySize.getSelectedItemPosition(); - switch (nKeyIndex) { - case 0: - mNewKeySize = 512; - break; - case 1: - mNewKeySize = 1024; - break; - case 2: - mNewKeySize = 2048; - break; - case 3: - mNewKeySize = 4096; - break; - } + final String selectedItem = (String) keySize.getSelectedItem(); + mNewKeySize = Integer.parseInt(selectedItem); } catch (NumberFormatException e) { mNewKeySize = 0; } @@ -145,7 +133,27 @@ public class CreateKeyDialogFragment extends DialogFragment { } }); - return dialog.create(); + final AlertDialog alertDialog = dialog.create(); + + final AdapterView.OnItemSelectedListener weakRsaListener = new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + final Choice selectedAlgorithm = (Choice) algorithm.getSelectedItem(); + final int selectedKeySize = Integer.parseInt((String) keySize.getSelectedItem()); + final boolean isWeakRsa = (selectedAlgorithm.getId() == Id.choice.algorithm.rsa && + selectedKeySize <= 1024); + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!isWeakRsa); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }; + + keySize.setOnItemSelectedListener(weakRsaListener); + algorithm.setOnItemSelectedListener(weakRsaListener); + + return alertDialog; } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index b067010df..b4c38184c 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -87,11 +87,11 @@ public class DeleteFileDialogFragment extends DialogFragment { false, null); - // Message is received after deleting is done in ApgService + // Message is received after deleting is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(activity, deletingDialog) { public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 1bcf5b33c..72ea4c013 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Dominik Schürmann + * Copyright (C) 2013-2014 Dominik Schürmann * * 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 @@ -20,161 +20,125 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.TextView; + import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.Log; -import java.util.ArrayList; +import java.util.HashMap; public class DeleteKeyDialogFragment extends DialogFragment { private static final String ARG_MESSENGER = "messenger"; - private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file"; - private static final String ARG_KEY_TYPE = "key_type"; + private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids"; public static final int MESSAGE_OKAY = 1; + public static final int MESSAGE_ERROR = 0; - public static final String MESSAGE_NOT_DELETED = "not_deleted"; + private boolean mIsSingleSelection = false; + + private TextView mMainMessage; + private CheckBox mCheckDeleteSecret; + private LinearLayout mDeleteSecretKeyView; + private View mInflateView; private Messenger mMessenger; /** * Creates new instance of this delete file dialog fragment */ - public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds, - int keyType) { + public static DeleteKeyDialogFragment newInstance(Messenger messenger, + long[] masterKeyIds) { DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); - args.putLongArray(ARG_DELETE_KEY_RING_ROW_IDS, keyRingRowIds); - args.putInt(ARG_KEY_TYPE, keyType); + args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds); + //We don't need the key type frag.setArguments(args); return frag; } - /** - * Creates dialog - */ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + final FragmentActivity activity = getActivity(); mMessenger = getArguments().getParcelable(ARG_MESSENGER); - final long[] keyRingRowIds = getArguments().getLongArray(ARG_DELETE_KEY_RING_ROW_IDS); - final int keyType = getArguments().getInt(ARG_KEY_TYPE); + final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); AlertDialog.Builder builder = new AlertDialog.Builder(activity); + + //Setup custom View to display in AlertDialog + LayoutInflater inflater = activity.getLayoutInflater(); + mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null); + builder.setView(mInflateView); + + mDeleteSecretKeyView = (LinearLayout) mInflateView.findViewById(R.id.deleteSecretKeyView); + mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage); + mCheckDeleteSecret = (CheckBox) mInflateView.findViewById(R.id.checkDeleteSecret); + builder.setTitle(R.string.warning); - if (keyRingRowIds.length == 1) { - Uri dataUri; - if (keyType == Id.type.public_key) { - dataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowIds[0])); - } else { - dataUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowIds[0])); - } - String userId = ProviderHelper.getUserId(activity, dataUri); + // If only a single key has been selected + if (masterKeyIds.length == 1) { + mIsSingleSelection = true; - builder.setMessage(getString( - keyType == Id.type.public_key ? R.string.key_deletion_confirmation - : R.string.secret_key_deletion_confirmation, userId)); + long masterKeyId = masterKeyIds[0]; + + HashMap data = ProviderHelper.getUnifiedData(activity, masterKeyId, new String[]{ + KeyRings.USER_ID, + KeyRings.HAS_SECRET + }, new int[] { ProviderHelper.FIELD_TYPE_STRING, ProviderHelper.FIELD_TYPE_INTEGER }); + String userId = (String) data.get(KeyRings.USER_ID); + boolean hasSecret = ((Long) data.get(KeyRings.HAS_SECRET)) == 1; + + // Hide the Checkbox and TextView since this is a single selection,user will be notified through message + mDeleteSecretKeyView.setVisibility(View.GONE); + // Set message depending on which key it is. + mMainMessage.setText(getString( + hasSecret ? R.string.secret_key_deletion_confirmation + : R.string.public_key_deletetion_confirmation, + userId)); } else { - builder.setMessage(R.string.key_deletion_confirmation_multi); + mDeleteSecretKeyView.setVisibility(View.VISIBLE); + mMainMessage.setText(R.string.key_deletion_confirmation_multi); } builder.setIcon(R.drawable.ic_dialog_alert_holo_light); builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - ArrayList notDeleted = new ArrayList(); + public void onClick(DialogInterface dialog, int which) { - if (keyType == Id.type.public_key) { - Uri queryUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(); - String[] projection = new String[]{ - KeychainContract.KeyRings._ID, // 0 - KeychainContract.KeyRings.MASTER_KEY_ID, // 1 - KeychainContract.UserIds.USER_ID // 2 - }; - - // make selection with all entries where _ID is one of the given row ids - String selection = KeychainDatabase.Tables.KEY_RINGS + "." + - KeychainContract.KeyRings._ID + " IN("; - String selectionIDs = ""; - for (int i = 0; i < keyRingRowIds.length; i++) { - selectionIDs += "'" + String.valueOf(keyRingRowIds[i]) + "'"; - if (i + 1 < keyRingRowIds.length) { - selectionIDs += ","; - } - } - selection += selectionIDs + ")"; - - Cursor cursor = activity.getContentResolver().query(queryUri, projection, - selection, null, null); - - long rowId; - long masterKeyId; - String userId; - try { - while (cursor != null && cursor.moveToNext()) { - rowId = cursor.getLong(0); - masterKeyId = cursor.getLong(1); - userId = cursor.getString(2); - - Log.d(Constants.TAG, "rowId: " + rowId + ", masterKeyId: " + masterKeyId - + ", userId: " + userId); - - // check if a corresponding secret key exists... - Cursor secretCursor = activity.getContentResolver().query( - KeychainContract.KeyRings - .buildSecretKeyRingsByMasterKeyIdUri( - String.valueOf(masterKeyId)), - null, null, null, null - ); - if (secretCursor != null && secretCursor.getCount() > 0) { - notDeleted.add(userId); - } else { - // it is okay to delete this key, no secret key found! - ProviderHelper.deletePublicKeyRing(activity, rowId); - } - if (secretCursor != null) { - secretCursor.close(); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } else { - for (long keyRowId : keyRingRowIds) { - ProviderHelper.deleteSecretKeyRing(activity, keyRowId); - } + boolean success = false; + for(long masterKeyId : masterKeyIds) { + int count = activity.getContentResolver().delete( + KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null + ); + success = count > 0; } - - dismiss(); - - if (notDeleted.size() > 0) { - Bundle data = new Bundle(); - data.putStringArrayList(MESSAGE_NOT_DELETED, notDeleted); - sendMessageToHandler(MESSAGE_OKAY, data); - } else { + if (success) { sendMessageToHandler(MESSAGE_OKAY, null); + } else { + sendMessageToHandler(MESSAGE_ERROR, null); } + dismiss(); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @@ -184,6 +148,7 @@ public class DeleteKeyDialogFragment extends DialogFragment { dismiss(); } }); + return builder.create(); } @@ -198,7 +163,6 @@ public class DeleteKeyDialogFragment extends DialogFragment { if (data != null) { msg.setData(data); } - try { mMessenger.send(msg); } catch (RemoteException e) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java index 271219fa3..a3feab959 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java @@ -24,10 +24,12 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.os.Bundle; +import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -59,10 +61,33 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor public static final int MESSAGE_OKAY = 1; public static final int MESSAGE_CANCEL = 2; + public static final String MESSAGE_DATA_PASSPHRASE = "passphrase"; + private Messenger mMessenger; private EditText mPassphraseEditText; private boolean mCanKB; + /** + * Shows passphrase dialog to cache a new passphrase the user enters for using it later for + * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks + * for a symmetric passphrase + */ + public static void show(FragmentActivity context, long keyId, Handler returnHandler) { + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + try { + PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(context, + messenger, keyId); + + passphraseDialog.show(context.getSupportFragmentManager(), "passphraseDialog"); + } catch (PgpGeneralException e) { + Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); + // send message to handler to start encryption directly + returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); + } + } + /** * Creates new instance of this dialog fragment * @@ -114,10 +139,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor secretKey = null; alert.setMessage(R.string.passphrase_for_symmetric_encryption); } else { - // TODO: by master key id??? - secretKey = PgpKeyHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity, - secretKeyId)); - // secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId)); + secretKey = ProviderHelper.getPGPSecretKeyRing(activity, secretKeyId).getSecretKey(); if (secretKey == null) { alert.setTitle(R.string.title_key_not_found); @@ -173,7 +195,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor return; } else { clickSecretKey = PgpKeyHelper.getKeyNum(ProviderHelper - .getPGPSecretKeyRingByKeyId(activity, secretKeyId), + .getPGPSecretKeyRingWithKeyId(activity, secretKeyId), curKeyIndex); curKeyIndex++; // does post-increment work like C? continue; @@ -209,7 +231,11 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor passphrase); } - sendMessageToHandler(MESSAGE_OKAY); + // also return passphrase back to activity + Bundle data = new Bundle(); + data.putString(MESSAGE_DATA_PASSPHRASE, passphrase); + + sendMessageToHandler(MESSAGE_OKAY, data); } }); @@ -278,4 +304,25 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor } } + /** + * Send message back to handler which is initialized in a activity + * + * @param what Message integer you want to send + */ + private void sendMessageToHandler(Integer what, Bundle data) { + Message msg = Message.obtain(); + msg.what = what; + if (data != null) { + msg.setData(data); + } + + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java index b501ba230..b6ff139df 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareQrCodeDialogFragment.java @@ -31,6 +31,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.QrCodeUtils; @@ -89,29 +90,33 @@ public class ShareQrCodeDialogFragment extends DialogFragment { if (mFingerprintOnly) { alert.setPositiveButton(R.string.btn_okay, null); - byte[] fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), dataUri); - String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, false); + byte[] blob = (byte[]) ProviderHelper.getGenericData( + getActivity(), KeyRings.buildUnifiedKeyRingUri(dataUri), + KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + if(blob == null) { + // TODO error handling?! + return null; + } + String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob); mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint); - content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; setQrCode(content); } else { mText.setText(R.string.share_qr_code_dialog_start); - // TODO + // TODO works, but long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri); - // get public keyring as ascii armored string ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString( - getActivity(), dataUri, new long[]{masterKeyId}); + getActivity(), new long[] { masterKeyId }); // TODO: binary? content = keyringArmored.get(0); // OnClickListener are set in onResume to prevent automatic dismissing of Dialogs - // http://stackoverflow.com/questions/2620444/how-to-prevent-a-dialog-from-closing-when-a-button-is-clicked + // http://bit.ly/O5vfaR alert.setPositiveButton(R.string.btn_next, null); alert.setNegativeButton(android.R.string.cancel, null); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java index 1cf510d3a..7b21c189d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java @@ -18,8 +18,10 @@ package org.sufficientlysecure.keychain.ui.widget; public interface Editor { public interface EditorListener { - public void onDeleted(Editor editor); + public void onDeleted(Editor editor, boolean wasNewItem); + public void onEdited(); } public void setEditorListener(EditorListener listener); + public boolean needsSaving(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java new file mode 100644 index 000000000..6b2f3bf06 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.beardedhen.androidbootstrap.FontAwesomeText; +import org.sufficientlysecure.keychain.R; + +/** + * Class representing a LinearLayout that can fold and hide it's content when pressed + * To use just add the following to your xml layout + + + + + + + + */ +public class FoldableLinearLayout extends LinearLayout { + + private FontAwesomeText mFoldableIcon; + private boolean mFolded; + private boolean mHasMigrated = false; + private Integer mShortAnimationDuration = null; + private TextView mFoldableTextView = null; + private LinearLayout mFoldableContainer = null; + private View mFoldableLayout = null; + + private String mFoldedIconName; + private String mUnFoldedIconName; + private String mFoldedLabel; + private String mUnFoldedLabel; + + public FoldableLinearLayout(Context context) { + super(context); + processAttributes(context, null); + } + + public FoldableLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + processAttributes(context, attrs); + } + + public FoldableLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs); + processAttributes(context, attrs); + } + + /** + * Load given attributes to inner variables, + * @param context + * @param attrs + */ + private void processAttributes(Context context, AttributeSet attrs) { + if (attrs != null) { + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.FoldableLinearLayout, 0, 0); + mFoldedIconName = a.getString(R.styleable.FoldableLinearLayout_foldedIcon); + mUnFoldedIconName = a.getString(R.styleable.FoldableLinearLayout_unFoldedIcon); + mFoldedLabel = a.getString(R.styleable.FoldableLinearLayout_foldedLabel); + mUnFoldedLabel = a.getString(R.styleable.FoldableLinearLayout_unFoldedLabel); + a.recycle(); + } + // If any attribute isn't found then set a default one + mFoldedIconName = (mFoldedIconName == null) ? "fa-chevron-right" : mFoldedIconName; + mUnFoldedIconName = (mUnFoldedIconName == null) ? "fa-chevron-down" : mUnFoldedIconName; + mFoldedLabel = (mFoldedLabel == null) ? context.getString(R.id.none) : mFoldedLabel; + mUnFoldedLabel = (mUnFoldedLabel == null) ? context.getString(R.id.none) : mUnFoldedLabel; + } + + @Override + protected void onFinishInflate() { + // if the migration has already happened + // there is no need to move any children + if (!mHasMigrated) { + migrateChildrenToContainer(); + mHasMigrated = true; + } + + initialiseInnerViews(); + + super.onFinishInflate(); + } + + /** + * Migrates Child views as declared in xml to the inner foldableContainer + */ + private void migrateChildrenToContainer() { + // Collect children of FoldableLinearLayout as declared in XML + int childNum = getChildCount(); + View[] children = new View[childNum]; + + for (int i = 0; i < childNum; i++) { + children[i] = getChildAt(i); + } + if (children[0].getId() == R.id.foldableControl) { + + } + + // remove all of them from FoldableLinearLayout + detachAllViewsFromParent(); + + // Inflate the inner foldable_linearlayout.xml + LayoutInflater inflator = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + mFoldableLayout = inflator.inflate(R.layout.foldable_linearlayout, this, true); + mFoldableContainer = (LinearLayout) mFoldableLayout.findViewById(R.id.foldableContainer); + + // Push previously collected children into foldableContainer. + for (int i = 0; i < childNum; i++) { + addView(children[i]); + } + } + + private void initialiseInnerViews() { + mFoldableIcon = (FontAwesomeText) mFoldableLayout.findViewById(R.id.foldableIcon); + mFoldableIcon.setIcon(mFoldedIconName); + mFoldableTextView = (TextView) mFoldableLayout.findViewById(R.id.foldableText); + mFoldableTextView.setText(mFoldedLabel); + + // retrieve and cache the system's short animation time + mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); + + LinearLayout foldableControl = (LinearLayout) mFoldableLayout.findViewById(R.id.foldableControl); + foldableControl.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mFolded = !mFolded; + if (mFolded) { + mFoldableIcon.setIcon(mUnFoldedIconName); + mFoldableContainer.setVisibility(View.VISIBLE); + AlphaAnimation animation = new AlphaAnimation(0f, 1f); + animation.setDuration(mShortAnimationDuration); + mFoldableContainer.startAnimation(animation); + mFoldableTextView.setText(mUnFoldedLabel); + + } else { + mFoldableIcon.setIcon(mFoldedIconName); + AlphaAnimation animation = new AlphaAnimation(1f, 0f); + animation.setDuration(mShortAnimationDuration); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { } + + @Override + public void onAnimationEnd(Animation animation) { + // making sure that at the end the container is completely removed from view + mFoldableContainer.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { } + }); + mFoldableContainer.startAnimation(animation); + mFoldableTextView.setText(mFoldedLabel); + } + } + }); + + } + + /** + * Adds provided child view to foldableContainer View + * @param child + */ + @Override + public void addView(View child) { + if (mFoldableContainer != null) { + mFoldableContainer.addView(child); + } + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java index 7e0acfa28..c7bd1c987 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java @@ -26,17 +26,30 @@ import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.*; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.DatePicker; +import android.widget.LinearLayout; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKey; -import org.sufficientlysecure.keychain.Id; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.Choice; import java.text.DateFormat; -import java.util.*; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import java.util.Vector; public class KeyEditor extends LinearLayout implements Editor, OnClickListener { private PGPSecretKey mKey; @@ -47,11 +60,30 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { BootstrapButton mDeleteButton; TextView mAlgorithm; TextView mKeyId; - Spinner mUsage; TextView mCreationDate; BootstrapButton mExpiryDateButton; GregorianCalendar mCreatedDate; GregorianCalendar mExpiryDate; + GregorianCalendar mOriginalExpiryDate = null; + CheckBox mChkCertify; + CheckBox mChkSign; + CheckBox mChkEncrypt; + CheckBox mChkAuthenticate; + int mUsage; + int mOriginalUsage; + boolean mIsNewKey; + + private CheckBox.OnCheckedChangeListener mCheckChanged = new CheckBox.OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if (mEditorListener != null) { + mEditorListener.onEdited(); + } + } + }; + private int mDatePickerResultCount = 0; private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = @@ -61,7 +93,20 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { if (mDatePickerResultCount++ == 0) { GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); date.set(year, monthOfYear, dayOfMonth); - setExpiryDate(date); + if (mOriginalExpiryDate != null) { + long numDays = (date.getTimeInMillis() / 86400000) - + (mOriginalExpiryDate.getTimeInMillis() / 86400000); + if (numDays == 0) { + setExpiryDate(mOriginalExpiryDate); + } else { + setExpiryDate(date); + } + } else { + setExpiryDate(date); + } + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } }; @@ -83,21 +128,17 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { mKeyId = (TextView) findViewById(R.id.keyId); mCreationDate = (TextView) findViewById(R.id.creation); mExpiryDateButton = (BootstrapButton) findViewById(R.id.expiry); - mUsage = (Spinner) findViewById(R.id.usage); - Choice choices[] = { - new Choice(Id.choice.usage.sign_only, getResources().getString( - R.string.choice_sign_only)), - new Choice(Id.choice.usage.encrypt_only, getResources().getString( - R.string.choice_encrypt_only)), - new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( - R.string.choice_sign_and_encrypt)), }; - ArrayAdapter adapter = new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); mDeleteButton = (BootstrapButton) findViewById(R.id.delete); mDeleteButton.setOnClickListener(this); + mChkCertify = (CheckBox) findViewById(R.id.chkCertify); + mChkCertify.setOnCheckedChangeListener(mCheckChanged); + mChkSign = (CheckBox) findViewById(R.id.chkSign); + mChkSign.setOnCheckedChangeListener(mCheckChanged); + mChkEncrypt = (CheckBox) findViewById(R.id.chkEncrypt); + mChkEncrypt.setOnCheckedChangeListener(mCheckChanged); + mChkAuthenticate = (CheckBox) findViewById(R.id.chkAuthenticate); + mChkAuthenticate.setOnCheckedChangeListener(mCheckChanged); setExpiryDate(null); @@ -109,7 +150,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); } /* - * Using custom DatePickerDialog which overrides the setTitle because + * Using custom DatePickerDialog which overrides the setTitle because * the DatePickerDialog title is buggy (unix warparound bug). * See: https://code.google.com/p/android/issues/detail?id=49066 */ @@ -125,6 +166,9 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { // Note: Ignore results after the first one - android sends multiples. if (mDatePickerResultCount++ == 0) { setExpiryDate(null); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } }); @@ -152,15 +196,17 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { super.onFinishInflate(); } - public void setCanEdit(boolean bCanEdit) { - if (!bCanEdit) { + public void setCanBeEdited(boolean canBeEdited) { + if (!canBeEdited) { mDeleteButton.setVisibility(View.INVISIBLE); - mUsage.setEnabled(false); mExpiryDateButton.setEnabled(false); + mChkSign.setEnabled(false); //certify is always disabled + mChkEncrypt.setEnabled(false); + mChkAuthenticate.setEnabled(false); } } - public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) { + public void setValue(PGPSecretKey key, boolean isMasterKey, int usage, boolean isNewKey) { mKey = key; mIsMasterKey = isMasterKey; @@ -175,47 +221,46 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { Vector choices = new Vector(); boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA); - if (!isElGamalKey) { - choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString( - R.string.choice_sign_only))); + if (isElGamalKey) { + mChkSign.setVisibility(View.INVISIBLE); + TableLayout table = (TableLayout) findViewById(R.id.table_keylayout); + TableRow row = (TableRow) findViewById(R.id.row_sign); + table.removeView(row); } - if (!mIsMasterKey && !isDSAKey) { - choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString( - R.string.choice_encrypt_only))); + if (isDSAKey) { + mChkEncrypt.setVisibility(View.INVISIBLE); + TableLayout table = (TableLayout) findViewById(R.id.table_keylayout); + TableRow row = (TableRow) findViewById(R.id.row_encrypt); + table.removeView(row); } - if (!isElGamalKey && !isDSAKey) { - choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( - R.string.choice_sign_and_encrypt))); - } - - ArrayAdapter adapter = new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); - - // Set value in choice dropdown to key - int selectId = 0; - if (PgpKeyHelper.isEncryptionKey(key)) { - if (PgpKeyHelper.isSigningKey(key)) { - selectId = Id.choice.usage.sign_and_encrypt; - } else { - selectId = Id.choice.usage.encrypt_only; - } + if (!mIsMasterKey) { + mChkCertify.setVisibility(View.INVISIBLE); + TableLayout table = (TableLayout) findViewById(R.id.table_keylayout); + TableRow row = (TableRow) findViewById(R.id.row_certify); + table.removeView(row); } else { - // set usage if it is predefined - if (usage != -1) { - selectId = usage; - } else { - selectId = Id.choice.usage.sign_only; - } - + TextView mLabelUsage2 = (TextView) findViewById(R.id.label_usage2); + mLabelUsage2.setVisibility(View.INVISIBLE); } - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == selectId) { - mUsage.setSelection(i); - break; + int selectId = 0; + mIsNewKey = isNewKey; + if (isNewKey) { + mUsage = usage; + mChkCertify.setChecked((usage & KeyFlags.CERTIFY_OTHER) == KeyFlags.CERTIFY_OTHER); + mChkSign.setChecked((usage & KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA); + mChkEncrypt.setChecked(((usage & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) || + ((usage & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)); + mChkAuthenticate.setChecked((usage & KeyFlags.AUTHENTICATION) == KeyFlags.AUTHENTICATION); + } else { + mUsage = PgpKeyHelper.getKeyUsage(key); + mOriginalUsage = mUsage; + if (key.isMasterKey()) { + mChkCertify.setChecked(PgpKeyHelper.isCertificationKey(key)); } + mChkSign.setChecked(PgpKeyHelper.isSigningKey(key)); + mChkEncrypt.setChecked(PgpKeyHelper.isEncryptionKey(key)); + mChkAuthenticate.setChecked(PgpKeyHelper.isAuthenticationKey(key)); } GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); @@ -228,6 +273,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } else { cal.setTime(PgpKeyHelper.getExpiryDate(key)); setExpiryDate(cal); + mOriginalExpiryDate = cal; } } @@ -241,7 +287,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { if (v == mDeleteButton) { parent.removeView(this); if (mEditorListener != null) { - mEditorListener.onDeleted(this); + mEditorListener.onDeleted(this, mIsNewKey); } } } @@ -273,9 +319,48 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { } public int getUsage() { - return ((Choice) mUsage.getSelectedItem()).getId(); + mUsage = (mUsage & ~KeyFlags.CERTIFY_OTHER) | + (mChkCertify.isChecked() ? KeyFlags.CERTIFY_OTHER : 0); + mUsage = (mUsage & ~KeyFlags.SIGN_DATA) | + (mChkSign.isChecked() ? KeyFlags.SIGN_DATA : 0); + mUsage = (mUsage & ~KeyFlags.ENCRYPT_COMMS) | + (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_COMMS : 0); + mUsage = (mUsage & ~KeyFlags.ENCRYPT_STORAGE) | + (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_STORAGE : 0); + mUsage = (mUsage & ~KeyFlags.AUTHENTICATION) | + (mChkAuthenticate.isChecked() ? KeyFlags.AUTHENTICATION : 0); + + return mUsage; } + public boolean needsSaving() { + if (mIsNewKey) { + return true; + } + + boolean retval = (getUsage() != mOriginalUsage); + + boolean dateChanged; + boolean mOEDNull = (mOriginalExpiryDate == null); + boolean mEDNull = (mExpiryDate == null); + if (mOEDNull != mEDNull) { + dateChanged = true; + } else { + if (mOEDNull) { + //both null, no change + dateChanged = false; + } else { + dateChanged = ((mExpiryDate.compareTo(mOriginalExpiryDate)) != 0); + } + } + retval |= dateChanged; + + return retval; + } + + public boolean getIsNewKey() { + return mIsNewKey; + } } class ExpiryDatePickerDialog extends DatePickerDialog { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java index f92c7532a..171763672 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java @@ -66,11 +66,16 @@ public class KeyServerEditor extends LinearLayout implements Editor, OnClickList if (v == mDeleteButton) { parent.removeView(this); if (mEditorListener != null) { - mEditorListener.onDeleted(this); + mEditorListener.onDeleted(this, false); } } } + @Override + public boolean needsSaving() { + return false; + } + public void setEditorListener(EditorListener listener) { mEditorListener = listener; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java index e5b6003b1..fb59cd3b7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java @@ -32,7 +32,10 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.spongycastle.openpgp.PGPKeyFlags; import org.spongycastle.openpgp.PGPSecretKey; + import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; @@ -44,23 +47,33 @@ import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.util.Choice; +import java.util.ArrayList; +import java.util.List; import java.util.Vector; -public class SectionView extends LinearLayout implements OnClickListener, EditorListener { +public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Editor { private LayoutInflater mInflater; private BootstrapButton mPlusButton; private ViewGroup mEditors; private TextView mTitle; private int mType = 0; + private EditorListener mEditorListener = null; private Choice mNewKeyAlgorithmChoice; private int mNewKeySize; - private boolean mCanEdit = true; + private boolean mOldItemDeleted = false; + private ArrayList mDeletedIDs = new ArrayList(); + private ArrayList mDeletedKeys = new ArrayList(); + private boolean mCanBeEdited = true; private ActionBarActivity mActivity; private ProgressDialogFragment mGeneratingDialog; + public void setEditorListener(EditorListener listener) { + mEditorListener = listener; + } + public SectionView(Context context) { super(context); mActivity = (ActionBarActivity) context; @@ -94,9 +107,9 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor } } - public void setCanEdit(boolean bCanEdit) { - mCanEdit = bCanEdit; - if (!mCanEdit) { + public void setCanBeEdited(boolean canBeEdited) { + mCanBeEdited = canBeEdited; + if (!mCanBeEdited) { mPlusButton.setVisibility(View.INVISIBLE); } } @@ -124,8 +137,26 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor /** * {@inheritDoc} */ - public void onDeleted(Editor editor) { + public void onDeleted(Editor editor, boolean wasNewItem) { + mOldItemDeleted |= !wasNewItem; + if (mOldItemDeleted) { + if (mType == Id.type.user_id) { + mDeletedIDs.add(((UserIdEditor) editor).getOriginalID()); + } else if (mType == Id.type.key) { + mDeletedKeys.add(((KeyEditor) editor).getValue()); + } + } this.updateEditorsVisible(); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } + } + + @Override + public void onEdited() { + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } protected void updateEditorsVisible() { @@ -133,20 +164,118 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); } + public boolean needsSaving() { + //check each view for needs saving, take account of deleted items + boolean ret = mOldItemDeleted; + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + ret |= editor.needsSaving(); + if (mType == Id.type.user_id) { + ret |= ((UserIdEditor) editor).primarySwapped(); + } + } + return ret; + } + + public boolean primaryChanged() { + boolean ret = false; + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + if (mType == Id.type.user_id) { + ret |= ((UserIdEditor) editor).primarySwapped(); + } + } + return ret; + } + + public String getOriginalPrimaryID() { + //NB: this will have to change when we change how Primary IDs are chosen, and so we need to be + // careful about where Master key capabilities are stored... multiple primaries and + // revoked ones make this harder than the simple case we are continuing to assume here + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + if (mType == Id.type.user_id) { + if (((UserIdEditor) editor).getIsOriginallyMainUserID()) { + return ((UserIdEditor) editor).getOriginalID(); + } + } + } + return null; + } + + public ArrayList getOriginalIDs() { + ArrayList orig = new ArrayList(); + if (mType == Id.type.user_id) { + for (int i = 0; i < mEditors.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i); + if (editor.isMainUserId()) { + orig.add(0, editor.getOriginalID()); + } else { + orig.add(editor.getOriginalID()); + } + } + return orig; + } else { + return null; + } + } + + public ArrayList getDeletedIDs() { + return mDeletedIDs; + } + + public ArrayList getDeletedKeys() { + return mDeletedKeys; + } + + public List getNeedsSavingArray() { + ArrayList mList = new ArrayList(); + for (int i = 0; i < mEditors.getChildCount(); ++i) { + Editor editor = (Editor) mEditors.getChildAt(i); + mList.add(editor.needsSaving()); + } + return mList; + } + + public List getNewIDFlags() { + ArrayList mList = new ArrayList(); + for (int i = 0; i < mEditors.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i); + if (editor.isMainUserId()) { + mList.add(0, editor.getIsNewID()); + } else { + mList.add(editor.getIsNewID()); + } + } + return mList; + } + + public List getNewKeysArray() { + ArrayList mList = new ArrayList(); + if (mType == Id.type.key) { + for (int i = 0; i < mEditors.getChildCount(); ++i) { + KeyEditor editor = (KeyEditor) mEditors.getChildAt(i); + mList.add(editor.getIsNewKey()); + } + } + return mList; + } + /** * {@inheritDoc} */ public void onClick(View v) { - if (mCanEdit) { + if (mCanBeEdited) { switch (mType) { case Id.type.user_id: { UserIdEditor view = (UserIdEditor) mInflater.inflate( R.layout.edit_key_user_id_item, mEditors, false); view.setEditorListener(this); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } + view.setValue("", mEditors.getChildCount() == 0, true); mEditors.addView(view); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } break; } @@ -185,18 +314,15 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, mEditors, false); view.setEditorListener(this); - view.setValue(userId); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } - view.setCanEdit(mCanEdit); + view.setValue(userId, mEditors.getChildCount() == 0, false); + view.setCanBeEdited(mCanBeEdited); mEditors.addView(view); } this.updateEditorsVisible(); } - public void setKeys(Vector list, Vector usages) { + public void setKeys(Vector list, Vector usages, boolean newKeys) { if (mType != Id.type.key) { return; } @@ -209,8 +335,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor false); view.setEditorListener(this); boolean isMasterKey = (mEditors.getChildCount() == 0); - view.setValue(list.get(i), isMasterKey, usages.get(i)); - view.setCanEdit(mCanEdit); + view.setValue(list.get(i), isMasterKey, usages.get(i), newKeys); + view.setCanBeEdited(mCanBeEdited); mEditors.addView(view); } @@ -256,11 +382,11 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor } }); - // Message is received after generating is done in ApgService + // Message is received after generating is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity, mGeneratingDialog) { public void handleMessage(Message message) { - // handle messages by standard ApgHandler first + // handle messages by standard KeychainIntentServiceHandler first super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { @@ -289,8 +415,15 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, false); view.setEditorListener(SectionView.this); - view.setValue(newKey, newKey.isMasterKey(), -1); + int usage = 0; + if (mEditors.getChildCount() == 0) { + usage = PGPKeyFlags.CAN_CERTIFY; + } + view.setValue(newKey, newKey.isMasterKey(), usage, true); mEditors.addView(view); SectionView.this.updateEditorsVisible(); + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java index ed81b162e..2253872d5 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java @@ -24,33 +24,36 @@ import android.util.Patterns; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.*; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RadioButton; import com.beardedhen.androidbootstrap.BootstrapButton; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ContactHelper; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import java.util.regex.Matcher; -import java.util.regex.Pattern; public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { private EditorListener mEditorListener = null; private BootstrapButton mDeleteButton; private RadioButton mIsMainUserId; + private String mOriginalID; private EditText mName; + private String mOriginalName; private AutoCompleteTextView mEmail; + private String mOriginalEmail; private EditText mComment; + private String mOriginalComment; + private boolean mOriginallyMainUserID; + private boolean mIsNewId; - public static class NoNameException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public NoNameException(String message) { - super(message); - } - } - - public void setCanEdit(boolean bCanEdit) { - if (!bCanEdit) { + public void setCanBeEdited(boolean canBeEdited) { + if (!canBeEdited) { mDeleteButton.setVisibility(View.INVISIBLE); mName.setEnabled(false); mIsMainUserId.setEnabled(false); @@ -59,14 +62,6 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene } } - public static class NoEmailException extends Exception { - static final long serialVersionUID = 0xf812773344L; - - public NoEmailException(String message) { - super(message); - } - } - public static class InvalidEmailException extends Exception { static final long serialVersionUID = 0xf812773345L; @@ -83,6 +78,23 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene super(context, attrs); } + TextWatcher mTextWatcher = new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void afterTextChanged(Editable s) { + if (mEditorListener != null) { + mEditorListener.onEdited(); + } + } + }; + @Override protected void onFinishInflate() { setDrawingCacheEnabled(true); @@ -94,8 +106,10 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene mIsMainUserId.setOnClickListener(this); mName = (EditText) findViewById(R.id.name); + mName.addTextChangedListener(mTextWatcher); mEmail = (AutoCompleteTextView) findViewById(R.id.email); mComment = (EditText) findViewById(R.id.comment); + mComment.addTextChangedListener(mTextWatcher); mEmail.setThreshold(1); // Start working from first character @@ -127,36 +141,45 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene // remove drawable if email is empty mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } }); super.onFinishInflate(); } - public void setValue(String userId) { + public void setValue(String userId, boolean isMainID, boolean isNewId) { + mName.setText(""); + mOriginalName = ""; mComment.setText(""); + mOriginalComment = ""; mEmail.setText(""); + mOriginalEmail = ""; + mIsNewId = isNewId; + mOriginalID = userId; - Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$"); - Matcher matcher = withComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mComment.setText(matcher.group(2)); - mEmail.setText(matcher.group(3)); - return; + String[] result = PgpKeyHelper.splitUserId(userId); + if (result[0] != null) { + mName.setText(result[0]); + mOriginalName = result[0]; + } + if (result[1] != null) { + mEmail.setText(result[1]); + mOriginalEmail = result[1]; + } + if (result[2] != null) { + mComment.setText(result[2]); + mOriginalComment = result[2]; } - Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$"); - matcher = withoutComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mEmail.setText(matcher.group(2)); - return; - } + mOriginallyMainUserID = isMainID; + setIsMainUserId(isMainID); } - public String getValue() throws NoNameException, NoEmailException { + public String getValue() { String name = ("" + mName.getText()).trim(); String email = ("" + mEmail.getText()).trim(); String comment = ("" + mComment.getText()).trim(); @@ -173,16 +196,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene // ok, empty one... return userId; } - - // otherwise make sure that name and email exist - if (name.equals("")) { - throw new NoNameException("need a name"); - } - - if (email.equals("")) { - throw new NoEmailException("need an email"); - } - + //TODO: check gpg accepts an entirely empty ID packet. specs say this is allowed return userId; } @@ -192,7 +206,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene boolean wasMainUserId = mIsMainUserId.isChecked(); parent.removeView(this); if (mEditorListener != null) { - mEditorListener.onDeleted(this); + mEditorListener.onDeleted(this, mIsNewId); } if (wasMainUserId && parent.getChildCount() > 0) { UserIdEditor editor = (UserIdEditor) parent.getChildAt(0); @@ -207,6 +221,9 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene editor.setIsMainUserId(false); } } + if (mEditorListener != null) { + mEditorListener.onEdited(); + } } } @@ -221,4 +238,28 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene public void setEditorListener(EditorListener listener) { mEditorListener = listener; } + + @Override + public boolean needsSaving() { + boolean retval = false; //(mOriginallyMainUserID != isMainUserId()); + retval |= !(mOriginalName.equals(("" + mName.getText()).trim())); + retval |= !(mOriginalEmail.equals(("" + mEmail.getText()).trim())); + retval |= !(mOriginalComment.equals(("" + mComment.getText()).trim())); + retval |= mIsNewId; + return retval; + } + + public boolean getIsOriginallyMainUserID() { + return mOriginallyMainUserID; + } + + public boolean primarySwapped() { + return (mOriginallyMainUserID != isMainUserId()); + } + + public String getOriginalID() { + return mOriginalID; + } + + public boolean getIsNewID() { return mIsNewId; } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java index 42fb03a3e..5efc732e4 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.util; -import android.text.Html; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; @@ -30,26 +29,30 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; + +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -import java.net.*; -import java.util.*; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * TODO: - * rewrite to use machine readable output. - *

- * see http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5 - * https://github.com/openpgp-keychain/openpgp-keychain/issues/259 - */ public class HkpKeyServer extends KeyServer { private static class HttpError extends Exception { private static final long serialVersionUID = 1718783705229428893L; @@ -74,16 +77,69 @@ public class HkpKeyServer extends KeyServer { private String mHost; private short mPort; - // example: - // pub 2048R/9F5C9090 2009-08-17 Jörg Runge - // <joerg@joergrunge.de> + /** + * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags% + *

    + *
  • %keyid% = this is either the fingerprint or the key ID of the key. + * Either the 16-digit or 8-digit key IDs are acceptable, but obviously the fingerprint is best. + *
  • + *
  • %algo% = the algorithm number, (i.e. 1==RSA, 17==DSA, etc). + * See RFC-2440
  • + *
  • %keylen% = the key length (i.e. 1024, 2048, 4096, etc.)
  • + *
  • %creationdate% = creation date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %expirationdate% = expiration date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %flags% = letter codes to indicate details of the key, if any. Flags may be in any + * order. The meaning of "disabled" is implementation-specific. Note that individual flags may + * be unimplemented, so the absence of a given flag does not necessarily mean the absence of the + * detail. + *
      + *
    • r == revoked
    • + *
    • d == disabled
    • + *
    • e == expired
    • + *
    + *
  • + *
+ * + * @see + * 5.2. Machine Readable Indexes + * in Internet-Draft OpenPGP HTTP Keyserver Protocol Document + */ public static final Pattern PUB_KEY_LINE = Pattern - .compile( - "pub +([0-9]+)([a-z]+)/.*?0x([0-9a-z]+).*? +([0-9-]+) +(.+)[\n\r]+((?: +.+[\n\r]+)*)", + .compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line + + "(uid:(.*):([0-9]+):([0-9]*):([rde]*))+", // one or more uid lines + Pattern.CASE_INSENSITIVE); + + /** + * uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags% + *
    + *
  • %escaped uid string% = the user ID string, with HTTP %-escaping for anything that + * isn't 7-bit safe as well as for the ":" character. Any other characters may be escaped, as + * desired.
  • + *
  • %creationdate% = creation date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %expirationdate% = expiration date of the key in standard + * RFC-2440 form (i.e. number of + * seconds since 1/1/1970 UTC time)
  • + *
  • %flags% = letter codes to indicate details of the key, if any. Flags may be in any + * order. The meaning of "disabled" is implementation-specific. Note that individual flags may + * be unimplemented, so the absence of a given flag does not necessarily mean the absence of + * the detail. + *
      + *
    • r == revoked
    • + *
    • d == disabled
    • + *
    • e == expired
    • + *
    + *
  • + *
+ */ + public static final Pattern UID_LINE = Pattern + .compile("uid:(.*):([0-9]+):([0-9]*):([rde]*)", Pattern.CASE_INSENSITIVE); - public static final Pattern USER_ID_LINE = Pattern.compile("^ +(.+)$", Pattern.MULTILINE - | Pattern.CASE_INSENSITIVE); private static final short PORT_DEFAULT = 11371; @@ -136,6 +192,7 @@ public class HkpKeyServer extends KeyServer { for (int i = 0; i < ips.length; ++i) { try { String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request; + Log.d(Constants.TAG, "hkp keyserver query: " + url); URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); conn.setConnectTimeout(5000); @@ -173,9 +230,9 @@ public class HkpKeyServer extends KeyServer { } catch (UnsupportedEncodingException e) { return null; } - String request = "/pks/lookup?op=index&search=" + encodedQuery; + String request = "/pks/lookup?op=index&options=mr&search=" + encodedQuery; - String data = null; + String data; try { data = query(request); } catch (HttpError e) { @@ -193,48 +250,64 @@ public class HkpKeyServer extends KeyServer { throw new QueryException("querying server(s) for '" + mHost + "' failed"); } - Matcher matcher = PUB_KEY_LINE.matcher(data); + final Matcher matcher = PUB_KEY_LINE.matcher(data); while (matcher.find()) { - ImportKeysListEntry info = new ImportKeysListEntry(); - info.bitStrength = Integer.parseInt(matcher.group(1)); - info.algorithm = matcher.group(2); - info.hexKeyId = "0x" + matcher.group(3); - info.keyId = PgpKeyHelper.convertHexToKeyId(matcher.group(3)); - String chunks[] = matcher.group(4).split("-"); + final ImportKeysListEntry entry = new ImportKeysListEntry(); - GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); - tmpGreg.set(Integer.parseInt(chunks[0]), Integer.parseInt(chunks[1]), - Integer.parseInt(chunks[2])); - info.date = tmpGreg.getTime(); - info.userIds = new ArrayList(); - if (matcher.group(5).startsWith("*** KEY")) { - info.revoked = true; + entry.setBitStrength(Integer.parseInt(matcher.group(3))); + + final int algorithmId = Integer.decode(matcher.group(2)); + entry.setAlgorithm(ImportKeysListEntry.getAlgorithmFromId(algorithmId)); + + // group 1 contains the full fingerprint (v4) or the long key id if available + // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr + String fingerprintOrKeyId = matcher.group(1); + if (fingerprintOrKeyId.length() > 16) { + entry.setFingerPrintHex(fingerprintOrKeyId.toLowerCase(Locale.US)); + entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length() + - 16, fingerprintOrKeyId.length())); } else { - String tmp = matcher.group(5).replaceAll("<.*?>", ""); - tmp = Html.fromHtml(tmp).toString(); - info.userIds.add(tmp); + // set key id only + entry.setKeyIdHex("0x" + fingerprintOrKeyId); } - if (matcher.group(6).length() > 0) { - Matcher matcher2 = USER_ID_LINE.matcher(matcher.group(6)); - while (matcher2.find()) { - String tmp = matcher2.group(1).replaceAll("<.*?>", ""); - tmp = Html.fromHtml(tmp).toString(); - info.userIds.add(tmp); - } - } - results.add(info); - } + final long creationDate = Long.parseLong(matcher.group(4)); + final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + tmpGreg.setTimeInMillis(creationDate * 1000); + entry.setDate(tmpGreg.getTime()); + + entry.setRevoked(matcher.group(6).contains("r")); + + ArrayList userIds = new ArrayList(); + final String uidLines = matcher.group(7); + final Matcher uidMatcher = UID_LINE.matcher(uidLines); + while (uidMatcher.find()) { + String tmp = uidMatcher.group(1).trim(); + if (tmp.contains("%")) { + try { + // converts Strings like "Universit%C3%A4t" to a proper encoding form "Universität". + tmp = (URLDecoder.decode(tmp, "UTF8")); + } catch (UnsupportedEncodingException ignored) { + // will never happen, because "UTF8" is supported + } + } + userIds.add(tmp); + } + entry.setUserIds(userIds); + + results.add(entry); + } return results; } @Override - public String get(long keyId) throws QueryException { + public String get(String keyIdHex) throws QueryException { HttpClient client = new DefaultHttpClient(); try { - HttpGet get = new HttpGet("http://" + mHost + ":" + mPort - + "/pks/lookup?op=get&search=" + PgpKeyHelper.convertKeyIdToHex(keyId)); - + String query = "http://" + mHost + ":" + mPort + + "/pks/lookup?op=get&options=mr&search=" + keyIdHex; + Log.d(Constants.TAG, "hkp keyserver get: " + query); + HttpGet get = new HttpGet(query); HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new QueryException("not found"); @@ -257,13 +330,14 @@ public class HkpKeyServer extends KeyServer { } @Override - public void add(String armoredText) throws AddKeyException { + public void add(String armoredKey) throws AddKeyException { HttpClient client = new DefaultHttpClient(); try { - HttpPost post = new HttpPost("http://" + mHost + ":" + mPort + "/pks/add"); - + String query = "http://" + mHost + ":" + mPort + "/pks/add"; + HttpPost post = new HttpPost(query); + Log.d(Constants.TAG, "hkp keyserver add: " + query); List nameValuePairs = new ArrayList(2); - nameValuePairs.add(new BasicNameValuePair("keytext", armoredText)); + nameValuePairs.add(new BasicNameValuePair("keytext", armoredKey)); post.setEntity(new UrlEncodedFormEntity(nameValuePairs)); HttpResponse response = client.execute(post); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java index 092c14e00..28cfa11f2 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/InputData.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2010-2014 Thialfihar + * * 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 diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java index b95b3ee6a..ae87deb31 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IntentIntegratorSupportV4.java @@ -16,9 +16,10 @@ package org.sufficientlysecure.keychain.util; +import com.google.zxing.integration.android.IntentIntegrator; + import android.content.Intent; import android.support.v4.app.Fragment; -import com.google.zxing.integration.android.IntentIntegrator; /** * IntentIntegrator for the V4 Android compatibility package. diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java index 40105df4f..3af674526 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java @@ -24,7 +24,7 @@ public class IterableIterator implements Iterable { public IterableIterator(Iterator iter, boolean failsafe) { mIter = iter; - if(failsafe && mIter == null) { + if (failsafe && mIter == null) { // is there a better way? mIter = new ArrayList().iterator(); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java index a31fdc5ae..7f70867a5 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeyServer.java @@ -46,7 +46,7 @@ public abstract class KeyServer { abstract List search(String query) throws QueryException, TooManyResponses, InsufficientQuery; - abstract String get(long keyId) throws QueryException; + abstract String get(String keyIdHex) throws QueryException; - abstract void add(String armoredText) throws AddKeyException; + abstract void add(String armoredKey) throws AddKeyException; } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java index 14b2a2211..b205bd556 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/KeychainServiceListener.java @@ -1,16 +1,20 @@ /* - * 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 + * Copyright (C) 2014 Dominik Schürmann * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 . */ + package org.sufficientlysecure.keychain.util; public interface KeychainServiceListener { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java index 377a8d5d6..5ec915810 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java @@ -17,7 +17,11 @@ package org.sufficientlysecure.keychain.util; -import java.util.concurrent.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java index a783d7820..4fcd3047f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/PositionAwareInputStream.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2010-2014 Thialfihar + * * 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 diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java new file mode 100644 index 000000000..23961c05f --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.util; + +/** This is a simple class that wraps a ProgressDialogUpdater, scaling the progress + * values into a specified range. + */ +public class ProgressScaler implements ProgressDialogUpdater { + + final ProgressDialogUpdater mWrapped; + final int mFrom, mTo, mMax; + + public ProgressScaler(ProgressDialogUpdater wrapped, int from, int to, int max) { + this.mWrapped = wrapped; + this.mFrom = from; + this.mTo = to; + this.mMax = max; + } + + /** + * Set progressDialogUpdater of ProgressDialog by sending message to handler on UI thread + */ + public void setProgress(String message, int progress, int max) { + mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax); + } + + public void setProgress(int resourceId, int progress, int max) { + mWrapped.setProgress(resourceId, progress, mMax); + } + + public void setProgress(int progress, int max) { + mWrapped.setProgress(progress, max); + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java index 8c3367bea..af9034aa7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/QrCodeUtils.java @@ -18,14 +18,16 @@ package org.sufficientlysecure.keychain.util; -import android.graphics.Bitmap; -import android.graphics.Color; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import android.graphics.Bitmap; +import android.graphics.Color; + import org.sufficientlysecure.keychain.Constants; import java.util.Hashtable; diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_person.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_person.png new file mode 100644 index 000000000..9fd81097b Binary files /dev/null and b/OpenPGP-Keychain/src/main/res/drawable-hdpi/ic_action_person.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.png index 571634090..f5487599b 100644 Binary files a/OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.png and b/OpenPGP-Keychain/src/main/res/drawable-hdpi/icon.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-hdpi/revoked_key_small.png b/OpenPGP-Keychain/src/main/res/drawable-hdpi/revoked_key_small.png new file mode 100644 index 000000000..75f45eb54 Binary files /dev/null and b/OpenPGP-Keychain/src/main/res/drawable-hdpi/revoked_key_small.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.png index 63bdba209..7cd482bff 100644 Binary files a/OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.png and b/OpenPGP-Keychain/src/main/res/drawable-ldpi/icon.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_person.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_person.png new file mode 100644 index 000000000..359da1c12 Binary files /dev/null and b/OpenPGP-Keychain/src/main/res/drawable-mdpi/ic_action_person.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.png index bab8c56bb..34f1420ac 100644 Binary files a/OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.png and b/OpenPGP-Keychain/src/main/res/drawable-mdpi/icon.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_person.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_person.png new file mode 100644 index 000000000..03eeb8d6a Binary files /dev/null and b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/ic_action_person.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.png index 79b8e27c6..32584f3ff 100644 Binary files a/OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.png and b/OpenPGP-Keychain/src/main/res/drawable-xhdpi/icon.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_person.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_person.png new file mode 100644 index 000000000..fd1bcdd45 Binary files /dev/null and b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/ic_action_person.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.png index ac8190c93..b2922309f 100644 Binary files a/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.png and b/OpenPGP-Keychain/src/main/res/drawable-xxhdpi/icon.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.png b/OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.png index cdc0fc9f0..93ea6b0f5 100644 Binary files a/OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.png and b/OpenPGP-Keychain/src/main/res/drawable-xxxhdpi/icon.png differ diff --git a/OpenPGP-Keychain/src/main/res/drawable/revoked_key_small.png b/OpenPGP-Keychain/src/main/res/drawable/revoked_key_small.png new file mode 100644 index 000000000..f9ed0596f Binary files /dev/null and b/OpenPGP-Keychain/src/main/res/drawable/revoked_key_small.png differ diff --git a/OpenPGP-Keychain/src/main/res/layout-large/api_apps_list_activity.xml b/OpenPGP-Keychain/src/main/res/layout-large/api_apps_list_activity.xml new file mode 100644 index 000000000..c0021261e --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout-large/api_apps_list_activity.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout-large/decrypt_activity.xml b/OpenPGP-Keychain/src/main/res/layout-large/decrypt_activity.xml new file mode 100644 index 000000000..26aed0831 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout-large/decrypt_activity.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout-large/encrypt_activity.xml b/OpenPGP-Keychain/src/main/res/layout-large/encrypt_activity.xml new file mode 100644 index 000000000..7d0d44074 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout-large/encrypt_activity.xml @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout-large/import_keys_activity.xml b/OpenPGP-Keychain/src/main/res/layout-large/import_keys_activity.xml new file mode 100644 index 000000000..2cb408441 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout-large/import_keys_activity.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout-large/key_list_activity.xml b/OpenPGP-Keychain/src/main/res/layout-large/key_list_activity.xml new file mode 100644 index 000000000..6636f12ff --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout-large/key_list_activity.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/api_account_settings_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_account_settings_activity.xml new file mode 100644 index 000000000..3557c1f00 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/api_account_settings_activity.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/layout/api_account_settings_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/api_account_settings_fragment.xml new file mode 100644 index 000000000..32843eb29 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/api_account_settings_fragment.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/api_accounts_adapter_list_item.xml b/OpenPGP-Keychain/src/main/res/layout/api_accounts_adapter_list_item.xml new file mode 100644 index 000000000..d31ae52d7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/api_accounts_adapter_list_item.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml index d83c8e87d..1377acf0e 100644 --- a/OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_activity.xml @@ -6,16 +6,28 @@ + + + + diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml index a8b68859b..96271d418 100644 --- a/OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/api_app_settings_fragment.xml @@ -2,6 +2,7 @@ @@ -35,64 +36,13 @@ android:textAppearance="?android:attr/textAppearanceMedium" /> - - - - - - - - - - - - - - - - - + android:layout_height="match_parent" + custom:foldedLabel="@string/api_settings_show_info" + custom:unFoldedLabel="@string/api_settings_hide_info" + custom:foldedIcon="fa-chevron-right" + custom:unFoldedIcon="fa-chevron-down"> - + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml index 71fbcfb12..9f95e9f3b 100644 --- a/OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/api_apps_list_activity.xml @@ -4,16 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - - - - + diff --git a/OpenPGP-Keychain/src/main/res/layout/api_apps_list_content.xml b/OpenPGP-Keychain/src/main/res/layout/api_apps_list_content.xml new file mode 100644 index 000000000..9f9b99045 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/api_apps_list_content.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/api_remote_create_account.xml b/OpenPGP-Keychain/src/main/res/layout/api_remote_create_account.xml new file mode 100644 index 000000000..3aee9094f --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/api_remote_create_account.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_error_message.xml b/OpenPGP-Keychain/src/main/res/layout/api_remote_error_message.xml similarity index 100% rename from OpenPGP-Keychain/src/main/res/layout/api_app_error_message.xml rename to OpenPGP-Keychain/src/main/res/layout/api_remote_error_message.xml diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_register_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_remote_register_app.xml similarity index 91% rename from OpenPGP-Keychain/src/main/res/layout/api_app_register_activity.xml rename to OpenPGP-Keychain/src/main/res/layout/api_remote_register_app.xml index aa9d59004..f85f3b8f7 100644 --- a/OpenPGP-Keychain/src/main/res/layout/api_app_register_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/api_remote_register_app.xml @@ -20,7 +20,7 @@ diff --git a/OpenPGP-Keychain/src/main/res/layout/api_app_select_pub_keys_activity.xml b/OpenPGP-Keychain/src/main/res/layout/api_remote_select_pub_keys.xml similarity index 100% rename from OpenPGP-Keychain/src/main/res/layout/api_app_select_pub_keys_activity.xml rename to OpenPGP-Keychain/src/main/res/layout/api_remote_select_pub_keys.xml diff --git a/OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml b/OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml index a2e908433..57a1b865f 100644 --- a/OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml +++ b/OpenPGP-Keychain/src/main/res/layout/create_key_dialog.xml @@ -17,6 +17,13 @@ android:padding="4dp" android:text="@string/key_creation_el_gamal_info" /> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/OpenPGP-Keychain/src/main/res/layout/decrypt_content.xml b/OpenPGP-Keychain/src/main/res/layout/decrypt_content.xml new file mode 100644 index 000000000..a496d8b9d --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/decrypt_content.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/decrypt_file_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/decrypt_file_fragment.xml new file mode 100644 index 000000000..633c9c832 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/decrypt_file_fragment.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/decrypt_message_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/decrypt_message_fragment.xml new file mode 100644 index 000000000..dfe1bf64a --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/decrypt_message_fragment.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/layout/decrypt_signature_include.xml b/OpenPGP-Keychain/src/main/res/layout/decrypt_signature_include.xml new file mode 100644 index 000000000..3e0d35c9b --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/decrypt_signature_include.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/drawer_list.xml b/OpenPGP-Keychain/src/main/res/layout/drawer_list.xml index 81ceba20c..ab00c0073 100644 --- a/OpenPGP-Keychain/src/main/res/layout/drawer_list.xml +++ b/OpenPGP-Keychain/src/main/res/layout/drawer_list.xml @@ -9,7 +9,7 @@ --> - + + + - + + + + + + + + + + + + + + + + @@ -122,4 +172,5 @@ android:layout_height="1dip" android:background="?android:attr/listDivider" /> - \ No newline at end of file + + diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml index 4fe65e341..6484c9b7b 100644 --- a/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_activity.xml @@ -6,399 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_asymmetric_fragment.xml new file mode 100644 index 000000000..fa1b03889 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_content.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_content.xml new file mode 100644 index 000000000..e719d07e1 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_content.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_content_adv_settings.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_content_adv_settings.xml new file mode 100644 index 000000000..ac990653a --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_content_adv_settings.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_file_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_file_fragment.xml new file mode 100644 index 000000000..efc4b4641 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_file_fragment.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_message_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_message_fragment.xml new file mode 100644 index 000000000..1fa338426 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_message_fragment.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/layout/encrypt_symmetric_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/encrypt_symmetric_fragment.xml new file mode 100644 index 000000000..89381e499 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/encrypt_symmetric_fragment.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/foldable_linearlayout.xml b/OpenPGP-Keychain/src/main/res/layout/foldable_linearlayout.xml new file mode 100644 index 000000000..2b863d52b --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/foldable_linearlayout.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml index d7794ace3..c82607a33 100644 --- a/OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/import_keys_activity.xml @@ -1,62 +1,10 @@ - - - - - - - - - - - - - - - - - - - + diff --git a/OpenPGP-Keychain/src/main/res/layout/import_keys_content.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_content.xml new file mode 100644 index 000000000..eb1333704 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/import_keys_content.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml b/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml index 3cc0bc6dc..f5a39f115 100644 --- a/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml +++ b/OpenPGP-Keychain/src/main/res/layout/import_keys_list_entry.xml @@ -15,7 +15,7 @@ limitations under the License. --> @@ -51,13 +51,6 @@ android:text="@string/label_main_user_id" android:textAppearance="?android:attr/textAppearanceMedium" /> - - + android:typeface="monospace" + android:layout_weight="1" /> + + - \ No newline at end of file + diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml index 65d246d7b..fcb376fa8 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_activity.xml @@ -4,16 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - - - - + diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_content.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_content.xml new file mode 100644 index 000000000..e58e42961 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_content.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml index 77bd6f4e9..f2430f213 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_list_fragment.xml @@ -11,7 +11,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="gone" + android:visibility="visible" android:gravity="center"> + android:orientation="vertical" + android:visibility="gone"> @@ -55,7 +57,6 @@ android:layout_height="match_parent" android:id="@+id/edit" android:focusable="false" - android:visibility="visible" android:enabled="true" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@color/black" @@ -71,7 +72,6 @@ android:textAppearance="?android:attr/textAppearanceSmall" android:text="@string/revoked" android:textColor="#e00" - android:visibility="visible" android:layout_gravity="center" /> diff --git a/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml b/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml index eddbe3cbf..b8897a7b3 100644 --- a/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml +++ b/OpenPGP-Keychain/src/main/res/layout/key_server_preference.xml @@ -6,7 +6,7 @@ android:orientation="vertical" > - \ No newline at end of file + diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_delete_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_delete_fragment.xml new file mode 100644 index 000000000..ef31f7690 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/layout/view_key_delete_fragment.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml index 9e4bc70eb..aecedc39b 100644 --- a/OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml +++ b/OpenPGP-Keychain/src/main/res/layout/view_key_keys_item.xml @@ -4,8 +4,7 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:paddingLeft="8dip" - android:paddingRight="3dip" - android:singleLine="true" > + android:paddingRight="3dip" > - + + + - + + + + + - \ No newline at end of file + diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml index 6ef3f3072..aa48252ce 100644 --- a/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/view_key_main_fragment.xml @@ -12,7 +12,8 @@ android:descendantFocusability="beforeDescendants" android:orientation="vertical" android:paddingLeft="16dp" - android:paddingRight="16dp"> + android:paddingRight="16dp" + android:id="@+id/container"> + + + + + + + - - - - - - - - - \ No newline at end of file + diff --git a/OpenPGP-Keychain/src/main/res/menu/api_account_settings.xml b/OpenPGP-Keychain/src/main/res/menu/api_account_settings.xml new file mode 100644 index 000000000..d08fc7f42 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/menu/api_account_settings.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml b/OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml index 82ee57a73..a21db6708 100644 --- a/OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml +++ b/OpenPGP-Keychain/src/main/res/menu/api_app_settings.xml @@ -6,9 +6,5 @@ android:id="@+id/menu_api_settings_revoke" android:title="@string/api_settings_revoke" app:showAsAction="never" /> - \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/menu/key_edit.xml b/OpenPGP-Keychain/src/main/res/menu/key_edit.xml index 16992affb..f9f7f8f0a 100644 --- a/OpenPGP-Keychain/src/main/res/menu/key_edit.xml +++ b/OpenPGP-Keychain/src/main/res/menu/key_edit.xml @@ -2,6 +2,12 @@ + + - + android:id="@+id/menu_key_list_add" + app:showAsAction="ifRoom|withText" + android:icon="@drawable/ic_action_add_person" + android:title="@string/menu_add_keys"> + + - + + + + + - - + app:showAsAction="ifRoom|withText" + android:icon="@drawable/ic_action_import_export" + android:title="@string/menu_export_keys"> + diff --git a/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml b/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml index db709052f..50f83026c 100644 --- a/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml +++ b/OpenPGP-Keychain/src/main/res/menu/key_list_multi.xml @@ -1,10 +1,6 @@ - + diff --git a/OpenPGP-Keychain/src/main/res/menu/key_view.xml b/OpenPGP-Keychain/src/main/res/menu/key_view.xml index cd84cc91a..105368cbb 100644 --- a/OpenPGP-Keychain/src/main/res/menu/key_view.xml +++ b/OpenPGP-Keychain/src/main/res/menu/key_view.xml @@ -52,7 +52,7 @@ android:id="@+id/menu_key_keyserver" android:icon="@drawable/ic_action_import_export" app:showAsAction="always" - android:title="@string/menu_share"> + android:title="@string/menu_key_server"> + + +

http://www.openkeychain.org

+

OpenKeychain is an OpenPGP implementation for Android.

+

Licence: GPLv3+

+ +

Developers OpenKeychain

+
    +
  • Dominik Schürmann (Hlavní vývojář)
  • +
  • Ash Hughes (crypto patches)
  • +
  • Brian C. Barnes
  • +
  • Bahtiar 'kalkin' Gadimov (UI)
  • +
  • Daniel Hammann
  • +
  • Daniel Haß
  • +
  • Greg Witczak
  • +
  • Miroojin Bakshi
  • +
  • Nikhil Peter Raj
  • +
  • Paul Sarbinowski
  • +
  • Sreeram Boyapati
  • +
  • Vincent Breitmoser
  • +
+

Developers APG 1.x

+
    +
  • Thialfihar (Lead developer)
  • +
  • 'Senecaso' (QRCode, sign key, upload key)
  • +
  • Markus Doits
  • +
+

Libraries

+ + + diff --git a/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_changelog.html new file mode 100644 index 000000000..db65b65f6 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_changelog.html @@ -0,0 +1,136 @@ + + + +

2.5

+
    +
  • fix decryption of symmetric pgp messages/files
  • +
  • refactored edit key screen (thanks to Ash Hughes)
  • +
  • OpenPGP API version 3 (multiple api accounts, internal fixes)
  • +
  • new modern design for encrypt/decrypt screens
  • +
+

2.4

+

Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

+
    +
  • new unified key list
  • +
  • colorized key fingerprint
  • +
  • support for keyserver ports
  • +
  • deactivate possibility to generate weak keys
  • +
  • much more internal work on the API
  • +
  • certify user ids
  • +
  • keyserver query based on machine-readable output
  • +
  • lock navigation drawer on tablets
  • +
  • suggestions for emails on creation of keys
  • +
  • search in public key lists
  • +
  • and much more improvements and fixes…
  • +
+

2.3.1

+
    +
  • hotfix for crash when upgrading from old versions
  • +
+

2.3

+
    +
  • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
  • +
  • fix setting expiry dates on keys (thanks to Ash Hughes)
  • +
  • more internal fixes when editing keys (thanks to Ash Hughes)
  • +
  • querying keyservers directly from the import screen
  • +
  • fix layout and dialog style on Android 2.2-3.0
  • +
  • fix crash on keys with empty user ids
  • +
  • fix crash and empty lists when coming back from signing screen
  • +
  • Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source
  • +
  • fix upload of key from signing screen
  • +
+

2.2

+
    +
  • new design with navigation drawer
  • +
  • new public key list design
  • +
  • new public key view
  • +
  • bug fixes for importing of keys
  • +
  • key cross-certification (thanks to Ash Hughes)
  • +
  • handle UTF-8 passwords properly (thanks to Ash Hughes)
  • +
  • first version with new languages (thanks to the contributors on Transifex)
  • +
  • sharing of keys via QR Codes fixed and improved
  • +
  • package signature verification for API
  • +
+

2.1.1

+
    +
  • API Updates, preparation for K-9 Mail integration
  • +
+

2.1

+
    +
  • lots of bug fixes
  • +
  • new API for developers
  • +
  • PRNG bug fix by Google
  • +
+

2.0

+
    +
  • complete redesign
  • +
  • share public keys via qr codes, nfc beam
  • +
  • sign keys
  • +
  • upload keys to server
  • +
  • fixes import issues
  • +
  • new AIDL API
  • +
+

1.0.8

+
    +
  • basic keyserver support
  • +
  • app2sd
  • +
  • more choices for pass phrase cache: 1, 2, 4, 8, hours
  • +
  • translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)
  • +
  • bugfixes
  • +
  • optimizations
  • +
+

1.0.7

+
    +
  • fixed problem with signature verification of texts with trailing newline
  • +
  • more options for pass phrase cache time to live (20, 40, 60 mins)
  • +
+

1.0.6

+
    +
  • account adding crash on Froyo fixed
  • +
  • secure file deletion
  • +
  • option to delete key file after import
  • +
  • stream encryption/decryption (gallery, etc.)
  • +
  • new options (language, force v3 signatures)
  • +
  • interface changes
  • +
  • bugfixes
  • +
+

1.0.5

+
    +
  • German and Italian translation
  • +
  • much smaller package, due to reduced BC sources
  • +
  • new preferences GUI
  • +
  • layout adjustment for localization
  • +
  • signature bugfix
  • +
+

1.0.4

+
    +
  • fixed another crash caused by some SDK bug with query builder
  • +
+

1.0.3

+
    +
  • fixed crashes during encryption/signing and possibly key export
  • +
+

1.0.2

+
    +
  • filterable key lists
  • +
  • smarter pre-selection of encryption keys
  • +
  • new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers
  • +
  • fixes and additional features (key preselection) for K-9 Mail, new beta build available
  • +
+

1.0.1

+
    +
  • GMail account listing was broken in 1.0.0, fixed again
  • +
+

1.0.0

+
    +
  • K-9 Mail integration, APG supporting beta build of K-9 Mail
  • +
  • support of more file managers (including ASTRO)
  • +
  • Slovenian translation
  • +
  • new database, much faster, less memory usage
  • +
  • defined Intents and content provider for other apps
  • +
  • bugfixes
  • +
+ + diff --git a/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_nfc_beam.html new file mode 100644 index 000000000..88492731c --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_nfc_beam.html @@ -0,0 +1,12 @@ + + + +

How to receive keys

+
    +
  1. Go to your partners contacts and open the contact you want to share.
  2. +
  3. Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.
  4. +
  5. After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.
  6. +
  7. Tap the card and the content will then load on the your device.
  8. +
+ + diff --git a/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_start.html b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_start.html new file mode 100644 index 000000000..0e60c17a7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/help_start.html @@ -0,0 +1,19 @@ + + + +

Getting started

+

First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

+ +

It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

+ +

I found a bug in OpenKeychain!

+

Please report the bug using the issue tracker of OpenKeychain.

+ +

Contribute

+

If you want to help us developing OpenKeychain by contributing code follow our small guide on Github.

+ +

Translations

+

Help translating OpenKeychain! Everybody can participate at OpenKeychain on Transifex.

+ + + diff --git a/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/nfc_beam_share.html new file mode 100644 index 000000000..083e055c7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-cs-rCZ/nfc_beam_share.html @@ -0,0 +1,11 @@ + + + +
    +
  1. Make sure that NFC is turned on in Settings > More > NFC and make sure that Android Beam is also on in the same section.
  2. +
  3. Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.
  4. +
  5. After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.
  6. +
  7. Tap the card and the content will then load on the other person’s device.
  8. +
+ + diff --git a/OpenPGP-Keychain/src/main/res/raw-de/help_about.html b/OpenPGP-Keychain/src/main/res/raw-de/help_about.html index 37d4193f7..7dc0ee7d9 100644 --- a/OpenPGP-Keychain/src/main/res/raw-de/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-de/help_about.html @@ -11,13 +11,19 @@
  • Ash Hughes (crypto patches)
  • Brian C. Barnes
  • Bahtiar 'kalkin' Gadimov (UI)
  • - +
  • Daniel Hammann
  • +
  • Daniel Haß
  • +
  • Greg Witczak
  • +
  • Miroojin Bakshi
  • +
  • Nikhil Peter Raj
  • +
  • Paul Sarbinowski
  • +
  • Sreeram Boyapati
  • +
  • Vincent Breitmoser
  • Entwickler APG 1.x

      -
    • 'Thialfihar' (Leitender Entwickler)
    • +
    • Thialfihar (Lead developer)
    • 'Senecaso' (QR-Code, Schlüssel signtieren, Schlüssel hochladen)
    • -
    • Oliver Runge
    • Markus Doits

    Bibliotheken

    @@ -38,8 +44,6 @@ HtmlTextView (Apache Lizenz v2)
  • Android AppMsg Bibliothek (Apache Lizenz v2)
  • -
  • Icons von RRZE Icon Set (Creative Commons Attribution Share-Alike Lizenz 3.0)
  • -
  • Icons von Tango Icon Set (Public Domain)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html index 1197869b5..dbf7afe76 100644 --- a/OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-de/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • @@ -8,7 +36,7 @@
    • more internal fixes when editing keys (thanks to Ash Hughes)
    • querying keyservers directly from the import screen
    • fix layout and dialog style on Android 2.2-3.0
    • -
    • fix crash on keys with empty user ids
    • +
    • Absturz bei leeren Nutzer IDs behoben
    • fix crash and empty lists when coming back from signing screen
    • Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source
    • fix upload of key from signing screen
    • @@ -38,15 +66,15 @@

      2.0

      • Komlett neu designd
      • -
      • share public keys via qr codes, nfc beam
      • +
      • Öffentliche Schlüssel teilen via QR Code, NFC Beam
      • Schlüssel signieren
      • Schlüssel auf den Server hochladen
      • -
      • fixes import issues
      • +
      • Importprobleme behoben
      • new AIDL API

      1.0.8

        -
      • basic keyserver support
      • +
      • Grundlegende Schlüsselserverunterstützung
      • app2sd
      • mehr Auswahlmöglichkeiten für den Passwortcache: 1, 2, 4, 8, Stunden
      • Übersetzungen: norwegisch (Danke, Sander Danielsen), chinesisch (danke, Zhang Fredrick)
      • @@ -98,8 +126,8 @@

        1.0.0

        • K-9 Mail integration, APG supporting beta build of K-9 Mail
        • -
        • support of more file managers (including ASTRO)
        • -
        • Slovenian translation
        • +
        • Unterstützung von mehr Filemanagern (einschließlich ASTRO)
        • +
        • Slowenische Übersetzung
        • Neue Datenbank, viel schneller, weniger Speicherbedarf
        • defined Intents and content provider for other apps
        • Fehlerbehebungen
        • diff --git a/OpenPGP-Keychain/src/main/res/raw-de/help_start.html b/OpenPGP-Keychain/src/main/res/raw-de/help_start.html index d2735f739..7a652682e 100644 --- a/OpenPGP-Keychain/src/main/res/raw-de/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-de/help_start.html @@ -1,15 +1,15 @@ -

          Getting started

          -

          First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

          +

          Los gehts

          +

          First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

          It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

          Ich habe einen Fehler in OpenKeychain gefunden!

          Please report the bug using the issue tracker of OpenKeychain.

          -

          Contribute

          +

          Unterstützen

          If you want to help us developing OpenKeychain by contributing code follow our small guide on Github.

          Übersetzungen

          diff --git a/OpenPGP-Keychain/src/main/res/raw-el/help_about.html b/OpenPGP-Keychain/src/main/res/raw-el/help_about.html index 863aeee58..ae7e16aae 100644 --- a/OpenPGP-Keychain/src/main/res/raw-el/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-el/help_about.html @@ -11,13 +11,19 @@
        • Ash Hughes (crypto patches)
        • Brian C. Barnes
        • Bahtiar 'kalkin' Gadimov (UI)
        • - +
        • Daniel Hammann
        • +
        • Daniel Haß
        • +
        • Greg Witczak
        • +
        • Miroojin Bakshi
        • +
        • Nikhil Peter Raj
        • +
        • Paul Sarbinowski
        • +
        • Sreeram Boyapati
        • +
        • Vincent Breitmoser

        Developers APG 1.x

          -
        • 'Thialfihar' (Lead developer)
        • +
        • Thialfihar (Lead developer)
        • 'Senecaso' (QRCode, sign key, upload key)
        • -
        • Oliver Runge
        • Markus Doits

        Libraries

        @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
      • Android AppMsg Library (Apache License v2)
      • -
      • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
      • -
      • Icons from Tango Icon Set (Public Domain)
      diff --git a/OpenPGP-Keychain/src/main/res/raw-el/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-el/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-el/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-el/help_changelog.html @@ -1,6 +1,34 @@ +

      2.5

      +
        +
      • fix decryption of symmetric pgp messages/files
      • +
      • refactored edit key screen (thanks to Ash Hughes)
      • +
      • OpenPGP API version 3 (multiple api accounts, internal fixes)
      • +
      • new modern design for encrypt/decrypt screens
      • +
      +

      2.4

      +

      Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

      +
        +
      • new unified key list
      • +
      • colorized key fingerprint
      • +
      • support for keyserver ports
      • +
      • deactivate possibility to generate weak keys
      • +
      • much more internal work on the API
      • +
      • certify user ids
      • +
      • keyserver query based on machine-readable output
      • +
      • lock navigation drawer on tablets
      • +
      • suggestions for emails on creation of keys
      • +
      • search in public key lists
      • +
      • and much more improvements and fixes…
      • +
      +

      2.3.1

      +
        +
      • hotfix for crash when upgrading from old versions
      • +

      2.3

      • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
      • diff --git a/OpenPGP-Keychain/src/main/res/raw-el/help_start.html b/OpenPGP-Keychain/src/main/res/raw-el/help_start.html index 3a6443a2f..0e60c17a7 100644 --- a/OpenPGP-Keychain/src/main/res/raw-el/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-el/help_start.html @@ -2,7 +2,7 @@

        Getting started

        -

        First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

        +

        First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

        It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

        diff --git a/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html index 863aeee58..ae7e16aae 100644 --- a/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_about.html @@ -11,13 +11,19 @@
      • Ash Hughes (crypto patches)
      • Brian C. Barnes
      • Bahtiar 'kalkin' Gadimov (UI)
      • - +
      • Daniel Hammann
      • +
      • Daniel Haß
      • +
      • Greg Witczak
      • +
      • Miroojin Bakshi
      • +
      • Nikhil Peter Raj
      • +
      • Paul Sarbinowski
      • +
      • Sreeram Boyapati
      • +
      • Vincent Breitmoser

      Developers APG 1.x

        -
      • 'Thialfihar' (Lead developer)
      • +
      • Thialfihar (Lead developer)
      • 'Senecaso' (QRCode, sign key, upload key)
      • -
      • Oliver Runge
      • Markus Doits

      Libraries

      @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
    • Android AppMsg Library (Apache License v2)
    • -
    • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
    • -
    • Icons from Tango Icon Set (Public Domain)
    diff --git a/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html index 3a6443a2f..0e60c17a7 100644 --- a/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-es-rCO/help_start.html @@ -2,7 +2,7 @@

      Getting started

      -

      First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-es/help_about.html b/OpenPGP-Keychain/src/main/res/raw-es/help_about.html index 95189425d..7a4f61127 100644 --- a/OpenPGP-Keychain/src/main/res/raw-es/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-es/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (Parches cryptográficos)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (UI)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Desarrolladores de APG 1.x

      -
    • 'Thialfihar' (Desarrollador principal)
    • +
    • Thialfihar (Desarrollador principal)
    • 'Senecaso' (Código QR, clave de firma, carga de clave)
    • -
    • Oliver Runge
    • Markus Doits

    Librerías

    @@ -38,8 +44,6 @@ HtmlTextView (Licencia Apache v2)
  • Librería Android AppMsg (Licencia Apache v2)
  • -
  • Icons de RRZE Icon Set (Creative Commons Attribution Compartir-Igual licencia 3.0)
  • -
  • Iconos de Tango Icon Set (Dominio Público)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-es/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-es/help_changelog.html index dfb51dc81..0b3db3a30 100644 --- a/OpenPGP-Keychain/src/main/res/raw-es/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-es/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • corregido descifrado de mensajes/ficheros con pgp simétrico
    • +
    • rediseñada la pantalla de edición de claves (gracias a Ash Hughes)
    • +
    • API OpenPGP versión 3 (multiples cuentas api, correcciones internas)
    • +
    • diseño más moderno para las pantallas de cifrado/descifrado
    • +
    +

    2.4

    +

    ¡Gracias a todos los solicitantes de Google Summer of Code 2014, por hacer esta aplicación productiva y libre de errores! +Además de varios parches pequeños, un notable número de correcciones fueron hechas por las siguientes personas (en orden alfabético): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • nueva lista unificada de claves
    • +
    • huella digital de la clave coloreada
    • +
    • compatibilidad con puertos del servidor de claves
    • +
    • desactivar la posibilidad de generar claves débiles
    • +
    • mucho más trabajo en el interior de la API
    • +
    • certificar las IDs de usuario
    • +
    • consulta al servidor de claves basadas ​​en lecturas mecánicas
    • +
    • cerrar navigation drawer en tabletas
    • +
    • sugerencias para emails en la creación de claves
    • +
    • buscar en las listas de claves públicas
    • +
    • y muchas más mejoras y correcciones...
    • +
    +

    2.3.1

    +
      +
    • corrección del fallo cuando se actualiza desde versiones anteriores
    • +

    2.3

    • elimina la exportación innecesaria de claves públicas cuando se exporta la clave secreta (gracias a Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-es/help_start.html b/OpenPGP-Keychain/src/main/res/raw-es/help_start.html index 2907bbc99..d56399ef0 100644 --- a/OpenPGP-Keychain/src/main/res/raw-es/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-es/help_start.html @@ -2,7 +2,7 @@

      Primeros pasos

      -

      Primero necesitas un par de claves personales. Crea una a través del menú "Mis claves" o importa un par de claves ya existentes a través de "Importar claves". Después, puedes descargar las claves de tus amigos o intercambiarlas a través de códigos QR o NFC.

      +

      Primero necesitas un par de claves personales. Crea una a través de las opciones del menú "Contactos" o importa un par de claves ya existentes a través de "Importar claves". Después, puedes descargar las claves de tus amigos o intercambiarlas a través de códigos QR o NFC.

      Es recomendable que instales OI File Manager para una mejor selección de archivos y Barcode Scanner para escanear los códigos QR generados. Pulsando en los enlaces se abrirá Google Play o F-Droid.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-et/help_about.html b/OpenPGP-Keychain/src/main/res/raw-et/help_about.html new file mode 100644 index 000000000..ae7e16aae --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-et/help_about.html @@ -0,0 +1,49 @@ + + + +

      http://www.openkeychain.org

      +

      OpenKeychain is an OpenPGP implementation for Android.

      +

      License: GPLv3+

      + +

      Developers OpenKeychain

      +
        +
      • Dominik Schürmann (Lead developer)
      • +
      • Ash Hughes (crypto patches)
      • +
      • Brian C. Barnes
      • +
      • Bahtiar 'kalkin' Gadimov (UI)
      • +
      • Daniel Hammann
      • +
      • Daniel Haß
      • +
      • Greg Witczak
      • +
      • Miroojin Bakshi
      • +
      • Nikhil Peter Raj
      • +
      • Paul Sarbinowski
      • +
      • Sreeram Boyapati
      • +
      • Vincent Breitmoser
      • +
      +

      Developers APG 1.x

      +
        +
      • Thialfihar (Lead developer)
      • +
      • 'Senecaso' (QRCode, sign key, upload key)
      • +
      • Markus Doits
      • +
      +

      Libraries

      + + + diff --git a/OpenPGP-Keychain/src/main/res/raw-et/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-et/help_changelog.html new file mode 100644 index 000000000..db65b65f6 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-et/help_changelog.html @@ -0,0 +1,136 @@ + + + +

      2.5

      +
        +
      • fix decryption of symmetric pgp messages/files
      • +
      • refactored edit key screen (thanks to Ash Hughes)
      • +
      • OpenPGP API version 3 (multiple api accounts, internal fixes)
      • +
      • new modern design for encrypt/decrypt screens
      • +
      +

      2.4

      +

      Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

      +
        +
      • new unified key list
      • +
      • colorized key fingerprint
      • +
      • support for keyserver ports
      • +
      • deactivate possibility to generate weak keys
      • +
      • much more internal work on the API
      • +
      • certify user ids
      • +
      • keyserver query based on machine-readable output
      • +
      • lock navigation drawer on tablets
      • +
      • suggestions for emails on creation of keys
      • +
      • search in public key lists
      • +
      • and much more improvements and fixes…
      • +
      +

      2.3.1

      +
        +
      • hotfix for crash when upgrading from old versions
      • +
      +

      2.3

      +
        +
      • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
      • +
      • fix setting expiry dates on keys (thanks to Ash Hughes)
      • +
      • more internal fixes when editing keys (thanks to Ash Hughes)
      • +
      • querying keyservers directly from the import screen
      • +
      • fix layout and dialog style on Android 2.2-3.0
      • +
      • fix crash on keys with empty user ids
      • +
      • fix crash and empty lists when coming back from signing screen
      • +
      • Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source
      • +
      • fix upload of key from signing screen
      • +
      +

      2.2

      +
        +
      • new design with navigation drawer
      • +
      • new public key list design
      • +
      • new public key view
      • +
      • bug fixes for importing of keys
      • +
      • key cross-certification (thanks to Ash Hughes)
      • +
      • handle UTF-8 passwords properly (thanks to Ash Hughes)
      • +
      • first version with new languages (thanks to the contributors on Transifex)
      • +
      • sharing of keys via QR Codes fixed and improved
      • +
      • package signature verification for API
      • +
      +

      2.1.1

      +
        +
      • API Updates, preparation for K-9 Mail integration
      • +
      +

      2.1

      +
        +
      • lots of bug fixes
      • +
      • new API for developers
      • +
      • PRNG bug fix by Google
      • +
      +

      2.0

      +
        +
      • complete redesign
      • +
      • share public keys via qr codes, nfc beam
      • +
      • sign keys
      • +
      • upload keys to server
      • +
      • fixes import issues
      • +
      • new AIDL API
      • +
      +

      1.0.8

      +
        +
      • basic keyserver support
      • +
      • app2sd
      • +
      • more choices for pass phrase cache: 1, 2, 4, 8, hours
      • +
      • translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)
      • +
      • bugfixes
      • +
      • optimizations
      • +
      +

      1.0.7

      +
        +
      • fixed problem with signature verification of texts with trailing newline
      • +
      • more options for pass phrase cache time to live (20, 40, 60 mins)
      • +
      +

      1.0.6

      +
        +
      • account adding crash on Froyo fixed
      • +
      • secure file deletion
      • +
      • option to delete key file after import
      • +
      • stream encryption/decryption (gallery, etc.)
      • +
      • new options (language, force v3 signatures)
      • +
      • interface changes
      • +
      • bugfixes
      • +
      +

      1.0.5

      +
        +
      • German and Italian translation
      • +
      • much smaller package, due to reduced BC sources
      • +
      • new preferences GUI
      • +
      • layout adjustment for localization
      • +
      • signature bugfix
      • +
      +

      1.0.4

      +
        +
      • fixed another crash caused by some SDK bug with query builder
      • +
      +

      1.0.3

      +
        +
      • fixed crashes during encryption/signing and possibly key export
      • +
      +

      1.0.2

      +
        +
      • filterable key lists
      • +
      • smarter pre-selection of encryption keys
      • +
      • new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers
      • +
      • fixes and additional features (key preselection) for K-9 Mail, new beta build available
      • +
      +

      1.0.1

      +
        +
      • GMail account listing was broken in 1.0.0, fixed again
      • +
      +

      1.0.0

      +
        +
      • K-9 Mail integration, APG supporting beta build of K-9 Mail
      • +
      • support of more file managers (including ASTRO)
      • +
      • Slovenian translation
      • +
      • new database, much faster, less memory usage
      • +
      • defined Intents and content provider for other apps
      • +
      • bugfixes
      • +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-et/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-et/help_nfc_beam.html new file mode 100644 index 000000000..88492731c --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-et/help_nfc_beam.html @@ -0,0 +1,12 @@ + + + +

      How to receive keys

      +
        +
      1. Go to your partners contacts and open the contact you want to share.
      2. +
      3. Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.
      4. +
      5. After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.
      6. +
      7. Tap the card and the content will then load on the your device.
      8. +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-et/help_start.html b/OpenPGP-Keychain/src/main/res/raw-et/help_start.html new file mode 100644 index 000000000..0e60c17a7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-et/help_start.html @@ -0,0 +1,19 @@ + + + +

      Getting started

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      + +

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      + +

      I found a bug in OpenKeychain!

      +

      Please report the bug using the issue tracker of OpenKeychain.

      + +

      Contribute

      +

      If you want to help us developing OpenKeychain by contributing code follow our small guide on Github.

      + +

      Translations

      +

      Help translating OpenKeychain! Everybody can participate at OpenKeychain on Transifex.

      + + + diff --git a/OpenPGP-Keychain/src/main/res/raw-et/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-et/nfc_beam_share.html new file mode 100644 index 000000000..083e055c7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-et/nfc_beam_share.html @@ -0,0 +1,11 @@ + + + +
        +
      1. Make sure that NFC is turned on in Settings > More > NFC and make sure that Android Beam is also on in the same section.
      2. +
      3. Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.
      4. +
      5. After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.
      6. +
      7. Tap the card and the content will then load on the other person’s device.
      8. +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_about.html b/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_about.html index 863aeee58..ae7e16aae 100644 --- a/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (crypto patches)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (UI)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Developers APG 1.x

      -
    • 'Thialfihar' (Lead developer)
    • +
    • Thialfihar (Lead developer)
    • 'Senecaso' (QRCode, sign key, upload key)
    • -
    • Oliver Runge
    • Markus Doits

    Libraries

    @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
  • Android AppMsg Library (Apache License v2)
  • -
  • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • -
  • Icons from Tango Icon Set (Public Domain)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_start.html b/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_start.html index f8c255232..93a305796 100644 --- a/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-fa-rIR/help_start.html @@ -2,7 +2,7 @@

      شروع کار

      -

      اول شما نیاز به یک جفت کلید شخصی دارید. از طریق منوها در "کلیدهای من" بسازید و یا از طریق"واردات کلیدهای" جفت کلیدهای موجود را وارد کنید. پس از آن، شما می توانید کلید های دوستان خود را دانلود کنید و یا آنها را از طریق کدهای QR یا NFC رد و بدل کنید.

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-fr/help_about.html b/OpenPGP-Keychain/src/main/res/raw-fr/help_about.html index 3cbbce4d5..00370c77e 100644 --- a/OpenPGP-Keychain/src/main/res/raw-fr/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-fr/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (correctif crypto)
    • Brian C. Barnes
    • Bahtiar « kalkin » Gadimov (interface utilisateur)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Les développeurs d'APG 1.x

      -
    • « Thialfihar (développeur principal)
    • +
    • Thialfihar (développeur principal)
    • « Senecaso » (Code QR, signer/téléverser la clef)
    • -
    • Oliver Runge
    • Markus Doits

    Bibliothèques

    @@ -38,8 +44,6 @@ HtmlTextView (Licence Apache v2)
  • Bibliothèque Android AppMsg (Licence Apache v2)
  • -
  • Icônes du jeu d'icônes RRZE (Licence Creative Commons Paternité - Partage des Conditions Initiales à l'Identique 3.0)
  • -
  • Icônes du jeu d'icônes Tango (domaine public)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html index c86c4a465..08a84c5fb 100644 --- a/OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-fr/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Merci à tous les participants de « Google Summer of Code 2014 » qui ont rendu cette version riche en fonctions et sans bogue ! +À part plusieurs petits correctifs, un nombre notable de correctifs ont été apportés par les personnes suivantes (par ordre alphabétique) : +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • Nouvelle liste de clefs unifiée
    • +
    • empreinte de clef colorée
    • +
    • prise en charge des ports du serveur de clefs
    • +
    • désactiver la possibilité de générer des clefs faibles
    • +
    • bien plus de travail interne sur l'API
    • +
    • certifier les ID des utilisateurs
    • +
    • requête du serveur de clef basée sur une sortie lisible par la machine
    • +
    • verrouiller le tiroir de navigation sur les tablettes
    • +
    • suggestions de courriels à la création des clefs
    • +
    • recherche dans les listes de clefs publiques
    • +
    • et bien plus d'améliorations et de correctifs...
    • +
    +

    2.3.1

    +
      +
    • correctif de plantage lors de la mise à niveau des anciennes versions
    • +

    2.3

    • supprimer l'exportation non nécessaire des clefs publiques lors de l'exportation d'une clef secrète
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-fr/help_start.html b/OpenPGP-Keychain/src/main/res/raw-fr/help_start.html index 0c1610f0c..ddaac44b1 100644 --- a/OpenPGP-Keychain/src/main/res/raw-fr/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-fr/help_start.html @@ -2,7 +2,7 @@

      Commencer

      -

      Vous avez d'abord besoin d'une paire de clefs personelles. Créez-en une avec l'option du menu « Mes clefs » ou importez des paires de clefs existantes avec « Importer des clefs ». Ensuite vous pouvez télécharger les clefs de vos amis, ou les échanger par codes QR ou NFC.

      +

      Il vous faut d'abord une paire de clefs personnelles. Créez-en une avec le menu des options dans « Contacts » ou importez des paires de clefs existantes avec « Importer des clefs ». Ensuite vous pouvez télécharger les clefs de vos amis, ou les échanger par codes QR ou NFC.

      Il vous est recommendé d'installer le gestionnaire de fichiers OI pour sa fonction améliorée de séléction des fichiers et le lecteur de codes à barres pour balayer les codes QR générés. Cliquer sur les liens ouvrira Google Play Store ou F-Droid pour l'installation.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html index ba0676f3e..8644d3fc6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (Patch crittografia)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (Interfaccia Utente)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Sviluppatori APG 1.x

      -
    • 'Thialfihar' (Capo Sviluppatore)
    • +
    • Thialfihar (Capo Sviluppatore)
    • 'Senecaso' (QRCode, firma chiavi, caricamento chiavi)
    • -
    • Oliver Runge
    • Markus Doits

    Librerie

    @@ -38,8 +44,6 @@ HtmlTextView (Licenza Apache v2)
  • Android AppMsg Library (Licenza Apache v2)
  • -
  • Icone da RRZE Icon Set (Licenza Creative Commons Attribution Share-Alike 3.0)
  • -
  • Icone da Tango Icon Set (Pubblico Dominio)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html index 050d2c9ef..abe268523 100644 --- a/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Grazie a tutti i partecipanti di Google Summer of Code 2014 che hanno reso questo rilascio ricco di caratteristiche e privo di bug! +Oltre a numerose piccole correzioni, un notevole numero di patch sono fatte dalle seguenti persone (in ordine alfabetico): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paolo Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • nuova lista chiave unificata
    • +
    • impronta chiave colorata
    • +
    • supporto per porte
    • +
    • disattiva la possibilità di generare chiavi deboli
    • +
    • molto più lavoro interno sulle API
    • +
    • certificazione ID utente
    • +
    • interrogazione keyserver basate su output leggibile a livello macchina
    • +
    • blocco del menu di navigazione sui tablet
    • +
    • suggerimenti per e-mail sulla creazione di chiavi
    • +
    • ricerca nelle liste di chiavi pubbliche
    • +
    • e molti altri miglioramenti e correzioni ...
    • +
    +

    2.3.1

    +
      +
    • correzione del crash quando si aggiorna da versioni precedenti
    • +

    2.3

    • rimossa esportazione non necessaria delle chiavi pubbliche quando si esportano le chiavi private (grazie a Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html index 4eadd82fc..0fd24178c 100644 --- a/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-it-rIT/help_start.html @@ -2,7 +2,7 @@

      Per iniziare

      -

      Per prima cosa hai bisogno di un paio di chiavi personali. Creane una tramite i menu di opzione sotto 'Mie Chiavi' o importane di esistenti attraverso "Importa Chiavi". Dopodiche' puoi scaricare le chiavi dei tuoi amici o scambiarle tramite Codici QR o NFC.

      +

      In primo luogo è necessario una coppia di chiavi personale. Creane una tramite l'opzione nel menu "Contatti" o importando coppie di chiavi esistenti tramite "Importa Chiavi". Successivamente, è possibile scaricare le chiavi dei vostri amici o scambiarle con i codici QR o NFC.

      Si raccomanda di installare OI File Manager per una migliore selezione dei file e Barcode Scanner per scansionare i codici QR. I collegamenti verranno aperti in Google Play Store o F-Droid per l'installazione.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-ja/help_about.html b/OpenPGP-Keychain/src/main/res/raw-ja/help_about.html index 206fc9f8d..e60add867 100644 --- a/OpenPGP-Keychain/src/main/res/raw-ja/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-ja/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (暗号関係パッチ提供)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (UI)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    APG 1.xの開発者達

      -
    • 'Thialfihar' (主任開発者)
    • +
    • Thialfihar (主任開発者)
    • 'Senecaso' (QRコード, 鍵署名, 鍵アップロード関係)
    • -
    • Oliver Runge
    • Markus Doits

    ライブラリ

    @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
  • Android AppMsg Library (Apache License v2)
  • -
  • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • -
  • Icons from Tango Icon Set (パブリックドメイン)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-ja/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-ja/help_changelog.html index 4878a3b55..f99656c1d 100644 --- a/OpenPGP-Keychain/src/main/res/raw-ja/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-ja/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    このリリースにおいて適用したリッチでバグのない機能を作ってくれたGoogle Summer of Code 2014の参加者たちに感謝を! +また、以下の人達(アルファベット順)の作ってくれたいくつもののさなパッチや相当数のパッチにも: +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • 新しい統合キーリスト
    • +
    • 鍵指紋のカラー化
    • +
    • 鍵サーバのポート設定のサポート
    • +
    • 弱い鍵の生成が可能だったのを無効化
    • +
    • さらなるAPIでの内部動作
    • +
    • ユーザーIDの検証
    • +
    • 鍵サーバへの要求をマシンリーダブル出力を基盤にした
    • +
    • タブレットでのナビゲーションドロワーのロック
    • +
    • 鍵の生成についてメールでのサジェスト
    • +
    • 公開鍵リスト内での検索
    • +
    • そしてさらなる改善と修正...
    • +
    +

    2.3.1

    +
      +
    • 古いバージョンからのアップデートでクラッシュすることに対するホットフィックス
    • +

    2.3

    • 秘密鍵のエクスポート時における必要でない公開鍵のエクスポートの削除 (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-ja/help_start.html b/OpenPGP-Keychain/src/main/res/raw-ja/help_start.html index 9764e876a..04ad31352 100644 --- a/OpenPGP-Keychain/src/main/res/raw-ja/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-ja/help_start.html @@ -2,7 +2,7 @@

      入門

      -

      最初にあなたの個人用鍵ペアが必要になります。オプションメニューの"自分の鍵"で生成するか、"鍵のインポート"から既存の鍵ペアをインポートします。その後、あなたの友人の鍵をダウンロード、もしくはQRコードやNFCで交換します。

      +

      最初にあなたの個人用鍵ペアが必要になります。オプションメニューの"連絡先"で生成するか、"鍵のインポート"から既存の鍵ペアをインポートします。その後、あなたの友人の鍵をダウンロード、もしくはQRコードやNFCで交換します。

      ファイルの選択を拡張するにはOI File ManagerBarcode Scannerを生成したQRコードのスキャンのため、それぞれのインストールを必要とします。 リンクをクリックして、Google Play Store上かF-Droidからインストールしてください。

      diff --git a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html index 863aeee58..ae7e16aae 100644 --- a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (crypto patches)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (UI)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Developers APG 1.x

      -
    • 'Thialfihar' (Lead developer)
    • +
    • Thialfihar (Lead developer)
    • 'Senecaso' (QRCode, sign key, upload key)
    • -
    • Oliver Runge
    • Markus Doits

    Libraries

    @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
  • Android AppMsg Library (Apache License v2)
  • -
  • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • -
  • Icons from Tango Icon Set (Public Domain)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html index 3a6443a2f..0e60c17a7 100644 --- a/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-nl-rNL/help_start.html @@ -2,7 +2,7 @@

      Getting started

      -

      First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-pl/help_about.html b/OpenPGP-Keychain/src/main/res/raw-pl/help_about.html new file mode 100644 index 000000000..a033c084a --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-pl/help_about.html @@ -0,0 +1,49 @@ + + + +

      http://www.openkeychain.org

      +

      OpenKeychain to implementacja OpenPGP na platformę Android.

      +

      Licencja: GPLv3+

      + +

      Deweloperzy OpenKeychain

      +
        +
      • Dominik Schürmann (Wiodący developer)
      • +
      • Ash Hughes (łatki crypto)
      • +
      • Brian C. Barnes
      • +
      • Bahtiar 'kalkin' Gadimov (Interfejs Użytkownika)
      • +
      • Daniel Hammann
      • +
      • Daniel Haß
      • +
      • Greg Witczak
      • +
      • Miroojin Bakshi
      • +
      • Nikhil Peter Raj
      • +
      • Paul Sarbinowski
      • +
      • Sreeram Boyapati
      • +
      • Vincent Breitmoser
      • +
      +

      Deweloperzy APG 1.x

      +
        +
      • Thialfihar (Wiodący deweloper)
      • +
      • 'Senecaso' (kody QR, podpisy kluczy, wysyłanie kluczy)
      • +
      • Markus Doits
      • +
      +

      Biblioteki

      + + + diff --git a/OpenPGP-Keychain/src/main/res/raw-pl/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-pl/help_changelog.html new file mode 100644 index 000000000..9d77a3e05 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-pl/help_changelog.html @@ -0,0 +1,136 @@ + + + +

      2.5

      +
        +
      • fix decryption of symmetric pgp messages/files
      • +
      • refactored edit key screen (thanks to Ash Hughes)
      • +
      • OpenPGP API version 3 (multiple api accounts, internal fixes)
      • +
      • new modern design for encrypt/decrypt screens
      • +
      +

      2.4

      +

      Podziękowania dla wszystkich kandydatów do Google Summer of Code 2014 którzy uczynili to wydanie bogatym w nowe funkcje i pozbawione błedów! +Poza kilkoma małymi poprawkami, znaczna ilość aktualizacji została wykonana przez poniższe osoby (w kolejności alfabetycznej): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

      +
        +
      • nowa ujednolicona lista kluczy
      • +
      • pokolowane odciski klucza
      • +
      • obsługa portów w serwerach kluczy
      • +
      • zablokowana możliwość generowania słabych kluczy
      • +
      • wiele wewnętrznych prac nad API
      • +
      • podpisywanie identyfikatorów użytkowników
      • +
      • zapytania do serwera kluczy wykorzystują wydajniejszą komunikację maszynową
      • +
      • zablokowany panel nawigacyjny na tabletach
      • +
      • podpowiedzi do adresu email przy tworzeniu kluczy
      • +
      • wyszukiwanie w liście publicznych kluczy
      • +
      • i wiele innych usprawnień i poprawek...
      • +
      +

      2.3.1

      +
        +
      • szybka poprawka awarii aplikacji przy aktualizacji ze starszej wersji
      • +
      +

      2.3

      +
        +
      • usunięto zbędne eksportowanie kluczy publicznych przy eksportowaniu kluczy prywatnych (podziękowania dla Ash Hughes)
      • +
      • naprawiono błąd z ustawianiem daty wygaśnięcia kluczy (podziękowania dla Ash Hugens)
      • +
      • więcej wewnętrznych poprawek przy edytowaniu kluczy (podziękowania dla Ash Hughes)
      • +
      • wysyłanie zapytań do serwera kluczy bezpośrednio z ekranu importu
      • +
      • poprawiony wygląd interfejsu i okienek na Androidzie 2.2-3.0
      • +
      • naprawiono awarię programu dla kluczy z pustym identyfikatorem użytkownika
      • +
      • naprawiono awarię aplikacji przy powrocie z ekranu podpisywania
      • +
      • Bouncy Castle (biblioteka kryptograficzna) zaktualizowana z wersji 1.47 do 1.50 i kompilowana ze źródeł
      • +
      • naprawiony błąd przy wysyłaniu klucza z ekranu podpisywania
      • +
      +

      2.2

      +
        +
      • nowy wygląd z panelem nawigacji
      • +
      • nowy wygląd listy kluczy publicznych
      • +
      • nowy widok klucza publicznego
      • +
      • naprawiono błędy związane z importowaniem kluczy
      • +
      • krzyżowa certyfikacja kluczy (podziękowania dla Ash Hughes)
      • +
      • hasła zapisane w UTF-8 są teraz prawidłowo obsługiwane (podziękowania dla Ash Hughes)
      • +
      • pierwsza wersja z nowymi językami (podziękowania dla tłumaczy-wolontariuszy z Transifex)
      • +
      • udostępnianie kluczy przez kody QR zostało poprawione i ulepszone
      • +
      • weryfikacja podpisu paczki dla API
      • +
      +

      2.1.1

      +
        +
      • aktualizacje API, przygotowanie do integracji z K-9 Mail
      • +
      +

      2.1

      +
        +
      • wiele poprawek błędów
      • +
      • nowe API dla programistów
      • +
      • Naprawiono błąd generatora liczb losowych (PRNG), Google.
      • +
      +

      2.0

      +
        +
      • kompletna przebudowa
      • +
      • udostępnianie kluczy publicznych przez kody QR oraz NFC
      • +
      • możliwość podpisywania kluczem
      • +
      • wysyłanie kluczy na serwer
      • +
      • naprawiono problemy związane z importowaniem
      • +
      • nowy AIDL API
      • +
      +

      1.0.8

      +
        +
      • podstawowa obsługa serwerów kluczy
      • +
      • app2sd
      • +
      • dodano więcej przedziałów czasowych zapamiętywania hasła: 1, 2, 4, 8 godzin
      • +
      • tłumaczenia: norweski (podziękowania dla Sander Danielsen), chiński (podziękowania dla Zhang Fredrick)
      • +
      • naprawione błędy
      • +
      • usprawnienia
      • +
      +

      1.0.7

      +
        +
      • naprawiono problem z weryfikowaniem podpisu tekstów kończących się znakiem nowej linii
      • +
      • dodano więcej przedziałów czasowych zapamiętywania hasła (20, 40, 60 minut)
      • +
      +

      1.0.6

      +
        +
      • naprawiono błąd powodujący awarię aplikacji przy dodawaniu nowego konta na Androidzie 2.2 Froyo
      • +
      • dodano bezpieczne usuwanie plików
      • +
      • Dodano możliwość usuwania plików kluczy po zaimportowaniu
      • +
      • możliwość strumieniowego szyfrowania/deszyfrowania (galeria i inne)
      • +
      • nowe opcje (języki, wymuszanie podpisów v3)
      • +
      • zmiany w interfejsie
      • +
      • naprawione błędy
      • +
      +

      1.0.5

      +
        +
      • tłumaczenie na niemiecki i włoski
      • +
      • znaczne zmniejszenie rozmiaru paczki, z powodu zredukowania źródeł BC
      • +
      • nowy interfejs graficzny Właściwości
      • +
      • usprawnienia wyglądu dla lokalizacji
      • +
      • naprawa błędu z podpisami
      • +
      +

      1.0.4

      +
        +
      • naprawiono kolejny błąd powodujący awarię aplikacji, spowodowany przez jakąś usterkę w SDK przy budowaniu zapytań
      • +
      +

      1.0.3

      +
        +
      • naprawiono błąd w trakcie szyfrowania/podpisywania i prawdopodobnie eksportowania klucza
      • +
      +

      1.0.2

      +
        +
      • dodano możliwość filtrowania listy kluczy
      • +
      • sprytniejsze automatyczne wybieranie kluczy szyfrujących
      • +
      • dodano nowy sposób obsługi intencji "wyświetl" i "wyślij", umożliwia szyfrowanie/deszyfrowanie plików wprost z menadżera plików.
      • +
      • poprawki i dodatkowe funkcje (podpowiedź wyboru klucza) dla K-9 Mail, nowe wydanie beta dostępne
      • +
      +

      1.0.1

      +
        +
      • wyświetlanie kont w GMailu było zepsute w 1.0.0, naprawiono je ponownie
      • +
      +

      1.0.0

      +
        +
      • integracja z K-9 Mail, APG obsługuje wersję beta K-9 Mail
      • +
      • dodano wsparcie dla większej liczby menadżerów plików (włącznie z ASTRO)
      • +
      • tłumaczenie na słoweński
      • +
      • Wykorzystanie nowej bazy danych, która jest znacznie szybsza i mniej pamięciożerna
      • +
      • zdefiniowano intecję i dostawców treści dla pozostałych aplikacji
      • +
      • naprawione błędy
      • +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-pl/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-pl/help_nfc_beam.html new file mode 100644 index 000000000..53db5e80c --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-pl/help_nfc_beam.html @@ -0,0 +1,12 @@ + + + +

      Jak odbierać klucze

      +
        +
      1. Wejdź do listy kontaktów Twojego partnera i otwórz kontakt, który chcesz przesłać.
      2. +
      3. Przytrzymaj oba urządzenia plecami do siebie (powinny się niemal dotykać) i poczujesz wibrację.
      4. +
      5. Po zakończeniu wibracji zobaczysz, że zawartość urządzenia partnera zamienia się w obiekt zbliżony do wizytówki, z animacją rodem ze Star Treka w tle.
      6. +
      7. Dotknij wizytówkę, a jej zawartość zostanie wysłana na Twoje urządzenie.
      8. +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-pl/help_start.html b/OpenPGP-Keychain/src/main/res/raw-pl/help_start.html new file mode 100644 index 000000000..e88a1ad6d --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-pl/help_start.html @@ -0,0 +1,19 @@ + + + +

      Pierwsze kroki

      +

      Po pierwsze potrzebujesz swoją osobistą parę kluczy. Stwórz ją, korzystając z odpowiedniej opcji w sekcji "Kontakty" albo zainportuj istniejącą parę korzystając z sekcji "Inportuj klucze". Następnie możesz porać klucze Twoich znajomych lub wymieniać się z nimi za pośrednictwem kodów QR lub technologii NFC.

      + +

      Zalecana jest instalacja menadżera plików OI File Manager w celu zapewnienia wygodniejszego wyboru plików oraz programu Barcode Scanner, który jest w stanie skanować wygenerowane kody QR. Kliknięcie na powyższe linki przekieruje Cię do sklepu Google Play / F-Droid.

      + +

      Znalazłem błąd w OpenKeychain!

      +

      Zgłoś błąd korzystając z systemu śledzenia błędów OpenKeychain.

      + +

      Wkład

      +

      Jeżeli chcesz pomóc nam rozwijać OpenKeychain jako programista, zapoznaj się z naszym małym poradnikiem na Githubie.

      + +

      Tłumaczenia

      +

      Pomóż przetłumaczyć OpenKeychain! Każdy może wziąć udział przez stronę OpenKeychain w serwisie Transifex.

      + + + diff --git a/OpenPGP-Keychain/src/main/res/raw-pl/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-pl/nfc_beam_share.html new file mode 100644 index 000000000..f17e44079 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-pl/nfc_beam_share.html @@ -0,0 +1,11 @@ + + + +
        +
      1. Upewnij się, że NFC (Near Field Communication, pol.: komunikacja bliskiego zasięgu) jest włączone. W tym celu wejdź w Ustawienia > Inne > NFC. Upewnij się również, że włączona jest funkcja Android Beam (znajduje się w tym samym miejscu).
      2. +
      3. Przytrzymaj oba urządzenia plecami do siebie (powinny się niemal dotykać) i poczujesz wibrację.
      4. +
      5. Po zakończeniu wibracji zobaczysz, że zawartość urządzenia partnera zamienia się w obiekt zbliżony do wizytówki, z animacją rodem ze Star Treka w tle.
      6. +
      7. Dotknij wizytówkę, a jej zawartość zostanie wysłana na urządzenie drugiej osoby.
      8. +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html index 863aeee58..ae7e16aae 100644 --- a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (crypto patches)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (UI)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Developers APG 1.x

      -
    • 'Thialfihar' (Lead developer)
    • +
    • Thialfihar (Lead developer)
    • 'Senecaso' (QRCode, sign key, upload key)
    • -
    • Oliver Runge
    • Markus Doits

    Libraries

    @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
  • Android AppMsg Library (Apache License v2)
  • -
  • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • -
  • Icons from Tango Icon Set (Public Domain)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html index 3a6443a2f..0e60c17a7 100644 --- a/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-pt-rBR/help_start.html @@ -2,7 +2,7 @@

      Getting started

      -

      First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-ru/help_about.html b/OpenPGP-Keychain/src/main/res/raw-ru/help_about.html index 655e98758..29cc1af83 100644 --- a/OpenPGP-Keychain/src/main/res/raw-ru/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-ru/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (патчи криптографии)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (UI)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Разработчики APG 1.x

      -
    • 'Thialfihar' (главный разработчик)
    • +
    • Thialfihar (главный разработчик)
    • 'Senecaso' (QR коды, подписание и загрузка ключей)
    • -
    • Oliver Runge
    • Markus Doits

    Компоненты

    @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
  • Библиотека Android AppMsg (Apache License v2)
  • -
  • Иконки RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • -
  • Иконки Tango Icon Set (Public Domain)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html index 2a324202f..0646e7dda 100644 --- a/OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-ru/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Спасибо всем участникам Google Summer of Code 2014, которые помогли сделать этот выпуск, добавив функции и исправив ошибки! +Из общего числа патчей, особенный вклад внесли следующие люди (в алфавитном порядке): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • новый объединенный список ключей
    • +
    • цветовая индикация отпечатков ключей
    • +
    • поддержка портов серверов ключей
    • +
    • отключена возможность создавать слабые ключи
    • +
    • ещё больше улучшений работы API
    • +
    • сертификация пользовательских данных
    • +
    • keyserver query based on machine-readable output
    • +
    • фиксация панели на планшетах
    • +
    • подсказки email при создании ключей
    • +
    • поиск в списках публичных ключей
    • +
    • и множество других исправлений и улучшений...
    • +
    +

    2.3.1

    +
      +
    • исправление ошибки при обновлении со старых версий
    • +

    2.3

    • удален не требующийся экспорт публичного ключа при экспорте секретного ключа (спасибо, Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-ru/help_start.html b/OpenPGP-Keychain/src/main/res/raw-ru/help_start.html index 9b2b99e96..78db598ec 100644 --- a/OpenPGP-Keychain/src/main/res/raw-ru/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-ru/help_start.html @@ -2,7 +2,7 @@

      Приступая

      -

      Для начала вам понадобится своя пара ключей. Воспользуйтесь меню в разделе "Мои ключи", что бы создать новую, или добавьте ранее созданную пару в разделе "Импорт ключей". После этого вы сможете скачать ключи ваших друзей или обменяться ключами посредством QR кодов или NFC.

      +

      Для начала вам понадобится своя пара ключей. Создайте её через меню раздела "Контакты" или импортируйте ранее созданный секретный ключ через меню "Импорт ключей". После этого вы сможете скачать ключи ваших друзей или обменяться ключами посредством QR кодов или NFC.

      Рекомендуется установить OI File Manager для удобного выбора файлов и Barcode Scanner для распознавания QR кодов. Перейдите по ссылкам на соответствующие страницы Google Play или F-Droid для дальнейшей установки.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html index 863aeee58..ae7e16aae 100644 --- a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (crypto patches)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (UI)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Developers APG 1.x

      -
    • 'Thialfihar' (Lead developer)
    • +
    • Thialfihar (Lead developer)
    • 'Senecaso' (QRCode, sign key, upload key)
    • -
    • Oliver Runge
    • Markus Doits

    Libraries

    @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
  • Android AppMsg Library (Apache License v2)
  • -
  • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • -
  • Icons from Tango Icon Set (Public Domain)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html index 3a6443a2f..0e60c17a7 100644 --- a/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-sl-rSI/help_start.html @@ -2,7 +2,7 @@

      Getting started

      -

      First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-tr/help_about.html b/OpenPGP-Keychain/src/main/res/raw-tr/help_about.html index eb262b242..7d2c24f9c 100644 --- a/OpenPGP-Keychain/src/main/res/raw-tr/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-tr/help_about.html @@ -11,13 +11,19 @@
    • Ash Hughes (kripto yamaları)
    • Brian C. Barnes
    • Bahtiar 'kalkin' Gadimov (Arayüz)
    • - +
    • Daniel Hammann
    • +
    • Daniel Haß
    • +
    • Greg Witczak
    • +
    • Miroojin Bakshi
    • +
    • Nikhil Peter Raj
    • +
    • Paul Sarbinowski
    • +
    • Sreeram Boyapati
    • +
    • Vincent Breitmoser

    Geliştiriciler APG 1.x

      -
    • 'Thialfihar' (Baş geliştirici)
    • +
    • Thialfihar (Lead developer)
    • 'Senecaso' (QR Kodu, anahtar imzalama, anahtar yükleme)
    • -
    • Oliver Runge
    • Markus Doits

    Kütüphaneler

    @@ -38,8 +44,6 @@ HtmlTextView (Apache License v2)
  • Android AppMsg Library (Apache License v2)
  • -
  • İkonlar RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • -
  • İkonlar Tango Icon Set (Public Domain)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-tr/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-tr/help_start.html b/OpenPGP-Keychain/src/main/res/raw-tr/help_start.html index 3a6443a2f..0e60c17a7 100644 --- a/OpenPGP-Keychain/src/main/res/raw-tr/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-tr/help_start.html @@ -2,7 +2,7 @@

      Getting started

      -

      First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-uk/help_about.html b/OpenPGP-Keychain/src/main/res/raw-uk/help_about.html index c8a5a82c5..b51b80617 100644 --- a/OpenPGP-Keychain/src/main/res/raw-uk/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-uk/help_about.html @@ -11,13 +11,19 @@
    • Аш Гюдж (латки шифрування)
    • Браян С. Барнс
    • Бахтіяр 'kalkin' Ґадімов (інтерфейс)
    • - +
    • Даніель Гаман
    • +
    • Даніель Габ
    • +
    • Ґреґ Вітчак
    • +
    • Міроджін Бакші
    • +
    • Ніхіл Петер Радж
    • +
    • Пауль Сарбіновський
    • +
    • Срірам Вояпаті
    • +
    • Вінсент Брейтмозер

    Розробники APG 1.x

      -
    • 'Thialfihar' (основний розробник)
    • +
    • Thialfihar (основний розробник)
    • 'Senecaso' (штрих-код, підпис і завантаження ключів)
    • -
    • Олівер Ранж
    • Маркус Дойтс

    Бібліотеки

    @@ -38,8 +44,6 @@ HtmlTextView (ліцензія Apache в.2)
  • Бібліотека Android AppMsg Library (Ліцензія Apache в. 2)
  • -
  • Піктограми із набору піктограм RRZE (ліцензія Creative Commons - Із зазначенням авторства - Розповсюдження на тих самих умовах - версія 3.0)
  • -
  • Піктограми із набору піктограм Tango (відкритий домен)
  • diff --git a/OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html index 0b67fa3a9..77e39d434 100644 --- a/OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-uk/help_changelog.html @@ -1,6 +1,34 @@ +

    2.5

    +
      +
    • виправлено опис симетричних повідомлень/файлів pgp
    • +
    • перероблено екран редагування ключа (завдяки Ash Hughes)
    • +
    • OpenPGP API версія 3 (підтримка кількох профілів, внутрішні зміни)
    • +
    • новий сучасний дизайн для екранів шифрування/розшифрування
    • +
    +

    2.4

    +

    Дякуємо усім заявникам Google Summer of Code 2014, які зробили цю версію багатшу на функції та вільну від помилок! +Крім окремих незначних латок, значне число латок зробили наступні люди (у алфавітному порядку): +Даніель Гаман, Даніель Габ, Ґреґ Вітчак, Міроджін Бакші, Ніхіл Петер Радж, Пауль Сарбіновський, Срірам Бояпаті, Вінсент Брейтмосер.

    +
      +
    • новий єдиний перелік ключів
    • +
    • кольоровий відбиток ключа
    • +
    • підтримка для портів сервера ключів
    • +
    • деактивувати можливість генерувати слабкі ключі
    • +
    • набагато більше внутрішньої роботи на API
    • +
    • сертифікувати ідентифікатори користувача
    • +
    • запит сервера ключів на основі машиночитабельного виводу
    • +
    • блокувати панель навігації на планшетах
    • +
    • пропозиції для листів при створенні ключів
    • +
    • пошук у списках відкритих ключів
    • +
    • і багато інших покращень та виправлень…
    • +
    +

    2.3.1

    +
      +
    • свіже виправлення збою при оновленні із старих версій
    • +

    2.3

    • видалений непотрібний експорт публічного ключа при експорті секретного ключа (завдяки Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw-uk/help_start.html b/OpenPGP-Keychain/src/main/res/raw-uk/help_start.html index 3bfb40f8a..45a3edb6a 100644 --- a/OpenPGP-Keychain/src/main/res/raw-uk/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-uk/help_start.html @@ -2,7 +2,7 @@

      Приступаючи до роботи

      -

      Спершу вам потрібна персональна в'язка ключів. Створіть одну через меню параметрів у "Мої Ключі" або імпортуйте наявні в'язки ключів через "Імпорт ключів". Після цього ви зможете завантажувати ключі ваших друзів чи обміняти їх через штрих-коди або NFC.

      +

      Спершу вам потрібна персональна в'язка ключів. Створіть одну через меню параметрів у "Контактах" або імпортуйте наявні в'язки ключів через "Імпорт ключів". Після цього ви зможете завантажувати ключі ваших друзів чи обміняти їх через штрих-коди або NFC.

      Рекомендуємо вам встановити OI File Manager для поліпшеного виділення файлів та Barcode Scanner для сканування згенерованих штрих-кодів. Натискання посилань відкриє Google Play або F-Droid для встановлення.

      diff --git a/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_about.html b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_about.html new file mode 100644 index 000000000..ae7e16aae --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_about.html @@ -0,0 +1,49 @@ + + + +

      http://www.openkeychain.org

      +

      OpenKeychain is an OpenPGP implementation for Android.

      +

      License: GPLv3+

      + +

      Developers OpenKeychain

      +
        +
      • Dominik Schürmann (Lead developer)
      • +
      • Ash Hughes (crypto patches)
      • +
      • Brian C. Barnes
      • +
      • Bahtiar 'kalkin' Gadimov (UI)
      • +
      • Daniel Hammann
      • +
      • Daniel Haß
      • +
      • Greg Witczak
      • +
      • Miroojin Bakshi
      • +
      • Nikhil Peter Raj
      • +
      • Paul Sarbinowski
      • +
      • Sreeram Boyapati
      • +
      • Vincent Breitmoser
      • +
      +

      Developers APG 1.x

      +
        +
      • Thialfihar (Lead developer)
      • +
      • 'Senecaso' (QRCode, sign key, upload key)
      • +
      • Markus Doits
      • +
      +

      Libraries

      + + + diff --git a/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_changelog.html new file mode 100644 index 000000000..db65b65f6 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_changelog.html @@ -0,0 +1,136 @@ + + + +

      2.5

      +
        +
      • fix decryption of symmetric pgp messages/files
      • +
      • refactored edit key screen (thanks to Ash Hughes)
      • +
      • OpenPGP API version 3 (multiple api accounts, internal fixes)
      • +
      • new modern design for encrypt/decrypt screens
      • +
      +

      2.4

      +

      Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

      +
        +
      • new unified key list
      • +
      • colorized key fingerprint
      • +
      • support for keyserver ports
      • +
      • deactivate possibility to generate weak keys
      • +
      • much more internal work on the API
      • +
      • certify user ids
      • +
      • keyserver query based on machine-readable output
      • +
      • lock navigation drawer on tablets
      • +
      • suggestions for emails on creation of keys
      • +
      • search in public key lists
      • +
      • and much more improvements and fixes…
      • +
      +

      2.3.1

      +
        +
      • hotfix for crash when upgrading from old versions
      • +
      +

      2.3

      +
        +
      • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
      • +
      • fix setting expiry dates on keys (thanks to Ash Hughes)
      • +
      • more internal fixes when editing keys (thanks to Ash Hughes)
      • +
      • querying keyservers directly from the import screen
      • +
      • fix layout and dialog style on Android 2.2-3.0
      • +
      • fix crash on keys with empty user ids
      • +
      • fix crash and empty lists when coming back from signing screen
      • +
      • Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source
      • +
      • fix upload of key from signing screen
      • +
      +

      2.2

      +
        +
      • new design with navigation drawer
      • +
      • new public key list design
      • +
      • new public key view
      • +
      • bug fixes for importing of keys
      • +
      • key cross-certification (thanks to Ash Hughes)
      • +
      • handle UTF-8 passwords properly (thanks to Ash Hughes)
      • +
      • first version with new languages (thanks to the contributors on Transifex)
      • +
      • sharing of keys via QR Codes fixed and improved
      • +
      • package signature verification for API
      • +
      +

      2.1.1

      +
        +
      • API Updates, preparation for K-9 Mail integration
      • +
      +

      2.1

      +
        +
      • lots of bug fixes
      • +
      • new API for developers
      • +
      • PRNG bug fix by Google
      • +
      +

      2.0

      +
        +
      • complete redesign
      • +
      • share public keys via qr codes, nfc beam
      • +
      • sign keys
      • +
      • upload keys to server
      • +
      • fixes import issues
      • +
      • new AIDL API
      • +
      +

      1.0.8

      +
        +
      • basic keyserver support
      • +
      • app2sd
      • +
      • more choices for pass phrase cache: 1, 2, 4, 8, hours
      • +
      • translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)
      • +
      • bugfixes
      • +
      • optimizations
      • +
      +

      1.0.7

      +
        +
      • fixed problem with signature verification of texts with trailing newline
      • +
      • more options for pass phrase cache time to live (20, 40, 60 mins)
      • +
      +

      1.0.6

      +
        +
      • account adding crash on Froyo fixed
      • +
      • secure file deletion
      • +
      • option to delete key file after import
      • +
      • stream encryption/decryption (gallery, etc.)
      • +
      • new options (language, force v3 signatures)
      • +
      • interface changes
      • +
      • bugfixes
      • +
      +

      1.0.5

      +
        +
      • German and Italian translation
      • +
      • much smaller package, due to reduced BC sources
      • +
      • new preferences GUI
      • +
      • layout adjustment for localization
      • +
      • signature bugfix
      • +
      +

      1.0.4

      +
        +
      • fixed another crash caused by some SDK bug with query builder
      • +
      +

      1.0.3

      +
        +
      • fixed crashes during encryption/signing and possibly key export
      • +
      +

      1.0.2

      +
        +
      • filterable key lists
      • +
      • smarter pre-selection of encryption keys
      • +
      • new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers
      • +
      • fixes and additional features (key preselection) for K-9 Mail, new beta build available
      • +
      +

      1.0.1

      +
        +
      • GMail account listing was broken in 1.0.0, fixed again
      • +
      +

      1.0.0

      +
        +
      • K-9 Mail integration, APG supporting beta build of K-9 Mail
      • +
      • support of more file managers (including ASTRO)
      • +
      • Slovenian translation
      • +
      • new database, much faster, less memory usage
      • +
      • defined Intents and content provider for other apps
      • +
      • bugfixes
      • +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_nfc_beam.html new file mode 100644 index 000000000..88492731c --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_nfc_beam.html @@ -0,0 +1,12 @@ + + + +

      How to receive keys

      +
        +
      1. Go to your partners contacts and open the contact you want to share.
      2. +
      3. Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.
      4. +
      5. After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.
      6. +
      7. Tap the card and the content will then load on the your device.
      8. +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_start.html b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_start.html new file mode 100644 index 000000000..0e60c17a7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/help_start.html @@ -0,0 +1,19 @@ + + + +

      Getting started

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      + +

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      + +

      I found a bug in OpenKeychain!

      +

      Please report the bug using the issue tracker of OpenKeychain.

      + +

      Contribute

      +

      If you want to help us developing OpenKeychain by contributing code follow our small guide on Github.

      + +

      Translations

      +

      Help translating OpenKeychain! Everybody can participate at OpenKeychain on Transifex.

      + + + diff --git a/OpenPGP-Keychain/src/main/res/raw-zh-rTW/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/nfc_beam_share.html new file mode 100644 index 000000000..083e055c7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/raw-zh-rTW/nfc_beam_share.html @@ -0,0 +1,11 @@ + + + +
        +
      1. Make sure that NFC is turned on in Settings > More > NFC and make sure that Android Beam is also on in the same section.
      2. +
      3. Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.
      4. +
      5. After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.
      6. +
      7. Tap the card and the content will then load on the other person’s device.
      8. +
      + + diff --git a/OpenPGP-Keychain/src/main/res/raw-zh/help_about.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_about.html index 863aeee58..813676ea2 100644 --- a/OpenPGP-Keychain/src/main/res/raw-zh/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_about.html @@ -3,24 +3,30 @@

      http://www.openkeychain.org

      OpenKeychain is an OpenPGP implementation for Android.

      -

      License: GPLv3+

      +

      授權:GPLv3+

      Developers OpenKeychain

      • Dominik Schürmann (Lead developer)
      • Ash Hughes (crypto patches)
      • Brian C. Barnes
      • -
      • Bahtiar 'kalkin' Gadimov (UI)
      • - +
      • Bahtiar 'kalkin' Gadimov (介面)
      • +
      • Daniel Hammann
      • +
      • Daniel Haß
      • +
      • Greg Witczak
      • +
      • Miroojin Bakshi
      • +
      • Nikhil Peter Raj
      • +
      • Paul Sarbinowski
      • +
      • Sreeram Boyapati
      • +
      • Vincent Breitmoser

      Developers APG 1.x

        -
      • 'Thialfihar' (Lead developer)
      • +
      • Thialfihar (Lead developer)
      • 'Senecaso' (QRCode, sign key, upload key)
      • -
      • Oliver Runge
      • Markus Doits
      -

      Libraries

      +

      函式庫

      diff --git a/OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html index abf660ba8..db65b65f6 100644 --- a/OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_changelog.html @@ -1,6 +1,34 @@ +

      2.5

      +
        +
      • fix decryption of symmetric pgp messages/files
      • +
      • refactored edit key screen (thanks to Ash Hughes)
      • +
      • OpenPGP API version 3 (multiple api accounts, internal fixes)
      • +
      • new modern design for encrypt/decrypt screens
      • +
      +

      2.4

      +

      Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

      +
        +
      • new unified key list
      • +
      • colorized key fingerprint
      • +
      • support for keyserver ports
      • +
      • deactivate possibility to generate weak keys
      • +
      • much more internal work on the API
      • +
      • certify user ids
      • +
      • keyserver query based on machine-readable output
      • +
      • lock navigation drawer on tablets
      • +
      • suggestions for emails on creation of keys
      • +
      • search in public key lists
      • +
      • and much more improvements and fixes…
      • +
      +

      2.3.1

      +
        +
      • hotfix for crash when upgrading from old versions
      • +

      2.3

      • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
      • diff --git a/OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html index 88492731c..7a90a794b 100644 --- a/OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html +++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_nfc_beam.html @@ -1,12 +1,12 @@ -

        How to receive keys

        +

        如何接收金要

          -
        1. Go to your partners contacts and open the contact you want to share.
        2. -
        3. Hold the two devices back to back (they have to be almost touching) and you’ll feel a vibration.
        4. -
        5. After it vibrates you’ll see the content on your partners device turn into a card-like object with Star Trek warp speed-looking animation in the background.
        6. -
        7. Tap the card and the content will then load on the your device.
        8. +
        9. 前往你夥伴裝置上的聯絡人清單,並點選你要分享的聯絡人。
        10. +
        11. 將兩部裝置背對背貼近(幾乎接觸),你會感覺到一股震動。
        12. +
        13. 震動之後你會看見你夥伴的畫面變成卡片狀,並且背景帶有如 Star Trek 般的特效。
        14. +
        15. 輕觸卡片,內容隨即顯示在你的裝置上。
        diff --git a/OpenPGP-Keychain/src/main/res/raw-zh/help_start.html b/OpenPGP-Keychain/src/main/res/raw-zh/help_start.html index 3a6443a2f..22ac99882 100644 --- a/OpenPGP-Keychain/src/main/res/raw-zh/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw-zh/help_start.html @@ -1,18 +1,18 @@ -

        Getting started

        -

        First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

        +

        快速上手

        +

        First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

        It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

        -

        I found a bug in OpenKeychain!

        -

        Please report the bug using the issue tracker of OpenKeychain.

        +

        我在OpenKeychain發現問題!

        +

        請利用 OpenKeychain 項目回報系統回報問題。

        -

        Contribute

        +

        發布

        If you want to help us developing OpenKeychain by contributing code follow our small guide on Github.

        -

        Translations

        +

        翻譯

        Help translating OpenKeychain! Everybody can participate at OpenKeychain on Transifex.

        diff --git a/OpenPGP-Keychain/src/main/res/raw-zh/nfc_beam_share.html b/OpenPGP-Keychain/src/main/res/raw-zh/nfc_beam_share.html index 083e055c7..99ffe4c12 100644 --- a/OpenPGP-Keychain/src/main/res/raw-zh/nfc_beam_share.html +++ b/OpenPGP-Keychain/src/main/res/raw-zh/nfc_beam_share.html @@ -2,10 +2,10 @@
          -
        1. Make sure that NFC is turned on in Settings > More > NFC and make sure that Android Beam is also on in the same section.
        2. -
        3. Hold the two devices back to back (they have to be almost touching) and you'll feel a vibration.
        4. -
        5. After it vibrates you'll see the content on your device turn into a card-like object with Star Trek warp speed-looking animation in the background.
        6. -
        7. Tap the card and the content will then load on the other person’s device.
        8. +
        9. 確定在 "設定" > "更多內容…" > "NFC" 裡面已經開啟 NFC 和 Android Beam。
        10. +
        11. 將兩部裝置背對背貼近(幾乎接觸),你會感覺到一股震動。
        12. +
        13. 震動之後你會看見你夥伴的畫面變成卡片狀,並且背景帶有如 Star Trek 般的特效。
        14. +
        15. 輕觸卡片,內容隨即顯示在你的裝置上。
        diff --git a/OpenPGP-Keychain/src/main/res/raw/help_about.html b/OpenPGP-Keychain/src/main/res/raw/help_about.html index 51e3f1325..847168446 100644 --- a/OpenPGP-Keychain/src/main/res/raw/help_about.html +++ b/OpenPGP-Keychain/src/main/res/raw/help_about.html @@ -15,13 +15,20 @@ And don't add newlines before or after p tags because of transifex -->
      • Ash Hughes (crypto patches)
      • Brian C. Barnes
      • Bahtiar 'kalkin' Gadimov (UI)
      • - +
      • Daniel Hammann
      • +
      • Daniel Haß
      • +
      • Greg Witczak
      • +
      • Miroojin Bakshi
      • +
      • Nikhil Peter Raj
      • +
      • Paul Sarbinowski
      • +
      • Sreeram Boyapati
      • +
      • Vincent Breitmoser
      +

      Developers APG 1.x

        -
      • 'Thialfihar' (Lead developer)
      • +
      • Thialfihar (Lead developer)
      • 'Senecaso' (QRCode, sign key, upload key)
      • -
      • Oliver Runge
      • Markus Doits
      @@ -35,8 +42,6 @@ And don't add newlines before or after p tags because of transifex -->
    • SpongyCastle (MIT X11 License)
    • HtmlTextView (Apache License v2)
    • Android AppMsg Library (Apache License v2)
    • -
    • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
    • -
    • Icons from Tango Icon Set (Public Domain)
    diff --git a/OpenPGP-Keychain/src/main/res/raw/help_changelog.html b/OpenPGP-Keychain/src/main/res/raw/help_changelog.html index 17ad853de..64a91e5f1 100644 --- a/OpenPGP-Keychain/src/main/res/raw/help_changelog.html +++ b/OpenPGP-Keychain/src/main/res/raw/help_changelog.html @@ -5,6 +5,37 @@ And don't add newlines before or after p tags because of transifex --> +

    2.5

    +
      +
    • fix decryption of symmetric pgp messages/files
    • +
    • refactored edit key screen (thanks to Ash Hughes)
    • +
    • new modern design for encrypt/decrypt screens
    • +
    • OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)
    • +
    + +

    2.4

    +

    Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free! +Besides several small patches, a notable number of patches are made by the following people (in alphabetical order): +Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.

    +
      +
    • new unified key list
    • +
    • colorized key fingerprint
    • +
    • support for keyserver ports
    • +
    • deactivate possibility to generate weak keys
    • +
    • much more internal work on the API
    • +
    • certify user ids
    • +
    • keyserver query based on machine-readable output
    • +
    • lock navigation drawer on tablets
    • +
    • suggestions for emails on creation of keys
    • +
    • search in public key lists
    • +
    • and much more improvements and fixes…
    • +
    + +

    2.3.1

    +
      +
    • hotfix for crash when upgrading from old versions
    • +
    +

    2.3

    • remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
    • diff --git a/OpenPGP-Keychain/src/main/res/raw/help_faq.html b/OpenPGP-Keychain/src/main/res/raw/help_faq.html index b3d5b3a11..bfd43eafd 100644 --- a/OpenPGP-Keychain/src/main/res/raw/help_faq.html +++ b/OpenPGP-Keychain/src/main/res/raw/help_faq.html @@ -5,8 +5,9 @@ And don't add newlines before or after p tags because of transifex --> -

      TODO

      -

      text

      +

      How can I specify connection port for Keyserver?

      +

      Add a new Keyserver (or modify existing one) by going to Preferences -> General -> Keyservers. Enter the port number after the Keyserver address and preceded it by a colon. For example, "p80.pool.sks-keyservers.net:80" (without quotation marks) means that server "p80.pool.sks-keyservers.net" is working on a port 80.

      +

      Default connection port is 11371 and it doesn't need to be specified.

      diff --git a/OpenPGP-Keychain/src/main/res/raw/help_start.html b/OpenPGP-Keychain/src/main/res/raw/help_start.html index 7afac0f08..56c02b1fd 100644 --- a/OpenPGP-Keychain/src/main/res/raw/help_start.html +++ b/OpenPGP-Keychain/src/main/res/raw/help_start.html @@ -6,7 +6,7 @@ And don't add newlines before or after p tags because of transifex -->

      Getting started

      -

      First you need a personal key pair. Create one via the option menus in "My Keys" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      +

      First you need a personal key pair. Create one via the option menus in "Contacts" or import existing key pairs via "Import Keys". Afterwards, you can download your friends' keys or exchange them via QR Codes or NFC.

      It is recommended that you install OI File Manager for enhanced file selection and Barcode Scanner to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.

      diff --git a/OpenPGP-Keychain/src/main/res/values-cs-rCZ/strings.xml b/OpenPGP-Keychain/src/main/res/values-cs-rCZ/strings.xml new file mode 100644 index 000000000..a2108f652 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/values-cs-rCZ/strings.xml @@ -0,0 +1,50 @@ + + + + Kontakty + Tajné klíče + Zvolit veřejný klíč + Zvolit tajný klíč + Zašifrovat + Dešifrovat + Heslo + Vytvořit klíč + Upravit klíč + Nastavení + Registrované aplikace + Nastavení serveru s klíči + Zadat heslo + Poslat zprávu... + Importovat klíče + Exportovat klíč + Exportovat klíče + Klíč nenalezen + Nahrát na server s klíči + Nápověda + + Klíče + + Podepsat + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/values-de/strings.xml b/OpenPGP-Keychain/src/main/res/values-de/strings.xml index 1e0fda3bc..c0ff7345e 100644 --- a/OpenPGP-Keychain/src/main/res/values-de/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-de/strings.xml @@ -13,8 +13,10 @@ Einstellungen Registrierte Anwendungen Schlüsselserver + Passphrase ändern Passwort setzen E-Mail senden... + Datei senden In eine Datei verschlüsseln In eine Datei entschlüsseln Schlüssel importieren @@ -53,8 +55,6 @@ Löschen Keine Okay - Passwort ändern - Passwort setzen Suchen Auf Schlüsselserver hochladen Weiter @@ -62,6 +62,8 @@ Zwischenablage Teilen mit… Schlüssel nachschlagen + Erweiterte Einstellungen anzeigen + Erweiterte Einstellungen verbergen Einstellungen Hilfe @@ -69,16 +71,17 @@ QR-Code Importieren NFC - Alle Schlüssel exportieren + Alle geheimen Schlüssel exportieren In Datei exportieren Schlüssel löschen Schlüssel erstellen Schlüssel erstellen (Experte) Suchen - Schlüsselserver + Schlüsselserver + Schlüsselserver… Von einem Schlüsselserver aktualisieren Auf Schlüsselserver hochladen - Teilen + Teilen… Teile Fingerabdruck… Teile gesamten Schlüssel… mit… @@ -91,6 +94,7 @@ Beam-Einstellungen Abbrechen Verschlüsseln nach… + Alles auswählen Signieren Nachricht @@ -103,6 +107,7 @@ Empfänger Nach Verschlüsselung löschen Nach Entschlüsselung löschen + Nach dem Verschlüsseln teilen Verschlüsselungsalgorithmus Hash-Algorithmus Öffentlicher Schlüssel @@ -124,6 +129,7 @@ Schlüssel nach Beglaubigung auf ausgewählten Schlüsselserver hochladen Fingerabdruck Auswählen + Ablaufdatum festsetzen %d ausgewählt %d ausgewählt @@ -131,11 +137,17 @@ <kein Name> <keine> <kein Schlüssel> + <Keine E-Mail> kann verschlüsseln kann signieren abgelaufen zurückgezogen + Benutzer ID + + 1 Kontakt + %d Kontakte + %d Schlüsselserver %d Schlüsselserver @@ -144,9 +156,6 @@ Privater Schlüssel: Keine - Nur Signieren - Nur Verschlüsseln - Signieren und Verschlüsseln 15 s 1 min 3 min @@ -166,21 +175,18 @@ Warnung Fehler Fehler: %s + Falsches Passwort. Verwende Inhalt der Zwischenablage. Zuerst ein Passwort setzen. Kein passender Dateimanager installiert. Die Passwörter stimmten nicht überein. - Leere Passwörter sind nicht erlaubt. Symmetrische Verschlüsselung. Passwort für \'%s\' eingeben %s\nwirklich löschen? Erfolgreich gelöscht. Zuerst eine Datei auswählen. - Erfolgreich entschlüsselt. - Erfolgreich verschlüsselt. - Erfolgreich in die Zwischenablage verschlüsselt. Passwort zweimal eingeben. Mindestens einen Schlüssel zum verschlüsseln auswählen. Mindestens einen Schlüssel zum Verschlüsseln oder einen zum Signieren auswählen. @@ -191,6 +197,7 @@ Soll der Schlüssel \'%s\' wirklich gelöscht werden?\nDies kann nicht rückgängig gemacht werden! Möchtest du wirklich alle ausgewählten Schlüssel löschen?\nDies kann nicht rückgängig gemacht werden! Soll der PRIVATE Schlüssel \'%s\' wirklich gelöscht werden?\nDies kann nicht rückgängig gemacht werden! + Private Schlüssel auch exportieren %d Schlüssel erfolgreich hinzugefügt %d Schlüssel erfolgreich hinzugefügt @@ -212,6 +219,7 @@ %d Schlüssel erfolgreich exportiert. Keine Schlüssel exportiert. Beachte: nur Unterschlüssel unterstützen ElGamal. Für ElGamal wird die am nächsten liegende Schlüssellänge von 1536, 2048, 3072, 4096 oder 8192 verwendet. + Beachte: RSA-Schlüssel mit einer Schlüssellänge von 1024-Bits oder weniger werden als unsicher angesehen und können daher nicht für neue Schlüssel erstellt werden. Schlüssel %08X konnte nicht gefunden werden. %d Schlüssel gefunden. @@ -243,6 +251,7 @@ Der Hauptschlüssel kann kein ElGamal Schlüssel sein Unbekannte Auswahl für Algorithmus ein Name muss angegeben werden + keine E-Mail gefunden eine E-Mail-Adresse muss angegeben werden Mindestens eine Benutzer-ID wird benötigt Hauptbenutzer-ID darf nicht leer sein @@ -265,48 +274,59 @@ Ablaufdatum muss später sein als das Erstellungsdatum Sie können diesen Kontakt nicht löschen, denn es ist ihr eigener. Sie können folgende Kontakte nicht löschen, denn sie gehören Ihnen selbst:\n%s + Unzureichende Serveranfrage Keyserveranfrage fehlgeschlagen + Zu viele Antworten + Datei ist leer + Ein allgemeiner Fehler trat auf, bitte schreiben Sie einen neuen Bugreport für OpenKeychain. Bitte lösche ihn unter \'Meine Schlüssel\'! Bitte lösche sie unter \'Meine Schlüssel\'! + + Ein Teil der geladenen Datei ist ein gültiges OpenPGP Objekt aber kein OpenPGP Schlüssel + Teile der geladenen Dateien sind gültige OpenPGP Objekte aber keine OpenPGP Schlüssel + - fertig. + Erledigt Abbrechen - speichern... - importieren... - exportieren... - erstelle Schlüssel, dies kann bis zu 3 Minuten dauern... - erstelle Schlüssel... - Hauptschlüssel wird vorbereitet... - Hauptschlüssel wird beglaubigt... - erstelle Hauptring... - füge Unterschlüssel hinzu... - Schlüssel wird gespeichert... + speichern… + importieren… + exportieren… + erstelle Schlüssel… + Hauptschlüssel wird vorbereitet… + Hauptschlüssel wird beglaubigt… + erstelle Hauptring… + füge Unterschlüssel hinzu… + Schlüssel wird gespeichert… Schlüssel wird exportiert… Schlüssel werden exportiert… - extrahiere Signaturschlüssel... - extrahiere Schlüssel... - Datenstrom wird vorbereitet... - Daten werden verschlüsselt... - Daten werden entschlüsselt... - Signatur wird vorbereitet... - Signatur wird erstellt... - Signatur wird verarbeitet... - Signatur wird verifiziert... - signiere... - Daten werden gelesen... - Schlüssel wird gesucht... - Daten werden entpackt... - Integrität wird überprüft... - \'%s\' wird sicher gelöscht... - Anfrage wird gestellt... + + erstelle Schlüssel, das kann bis zu 3 Minuten dauern… + erstelle Schlüssel, das kann bis zu 3 Minuten dauern… + + extrahiere Signaturschlüssel… + extrahiere Schlüssel… + Datenstrom wird vorbereitet… + Daten werden verschlüsselt… + Daten werden entschlüsselt… + Signatur wird vorbereitet… + Signatur wird erstellt… + Signatur wird verarbeitet… + Signatur wird verifiziert… + signiere… + Daten werden gelesen… + Schlüssel wird gesucht… + Daten werden entpackt… + Integrität wird überprüft… + \'%s\' wird sicher gelöscht… + Anfrage wird gestellt… Öffentliche Schlüssel suchen Private Schlüssel suchen - Teile Schlüssel über... + Teile Schlüssel über… 512 1024 @@ -317,6 +337,7 @@ sehr langsam Start + FAQ NFC-Beam Changelog Über @@ -338,6 +359,10 @@ Hilfe Füge den Schlüssel aus der Zwischenablage ein + Datei mit OpenKeychain entschlüsseln + Schlüssel mit OpenKeychain importieren + Mit OpenKeychain verschlüsseln + Mit OpenKeychain entschlüsseln Keine registrierten Anwendungen vorhanden!\n\nAnwendungen von Dritten können Zugriff auf OpenKeychain erbitten. Nachdem Zugriff gewährt wurde, werden diese hier aufgelistet. Erweiterte Einstellungen anzeigen @@ -356,7 +381,7 @@ Für diese Benutzer-IDs wurden keine öffentlichen Schlüssel gefunden: Für diese Benutzer-IDs existieren mehrere öffentliche Schlüssel: Bitte die Liste der Empfänger überprüfen! - Signaturüberprüfung fehlgeschlagen! Haben Sie diese App von einer anderen Quelle installiert? Wenn Sie eine Attacke ausschließen können, sollten Sie die Registrierung der App in OpenKeychain widerrufen und die App erneut registrieren. + Signaturüberprüfung fehlgeschlagen! Haben Sie diese App von einer anderen Quelle installiert? Wenn Sie eine Attacke ausschliessen können, sollten Sie die Registrierung der App in OpenKeychain widerrufen und die App erneut registrieren. Über QR Code teilen Mit \'Weiter\' durch alle QR-Codes gehen und diese nacheinander scannen. @@ -374,17 +399,23 @@ deinen eigenen Schlüssel erstellst existierende Schlüssel importierst. + Diesen Schlüssel bearbeiten Für diesen Kontakt verschlüsseln Schlüssel dieses Kontakts beglaubigen Info Zertifikationen - Kontakte - Verschlüsseln - Entschlüsseln Schlüssel Importieren Meine Schlüssel Registrierte Anwendungen Menu öffnen Menu schließen + Bearbeiten + Meine Schlüssel + Geheime Schlüssel + verfügbar + nicht verfügbar + Benutzer-IDs, die beglaubigt werden sollen + Wiederhinzufügen der Zertifikate + diff --git a/OpenPGP-Keychain/src/main/res/values-el/strings.xml b/OpenPGP-Keychain/src/main/res/values-el/strings.xml index 84b39c221..824399722 100644 --- a/OpenPGP-Keychain/src/main/res/values-el/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-el/strings.xml @@ -15,8 +15,6 @@ Διαγραφή Κανένα ΟΚ - Αλλαγή κωδικού - Επέλεξε Κωδικό Διαγραφής κλειδιού Δημιουργίας κλειδιού @@ -34,6 +32,7 @@ Ηλεκτρονικό ταχυδρομίο + + diff --git a/OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml b/OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml index 41dc629aa..11d84cdee 100644 --- a/OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-es-rCO/strings.xml @@ -36,8 +36,6 @@ Borrar Ninguno Ok - Cambiar contraseña - Establecer contraseña Buscar Siguiente Atrás @@ -46,7 +44,6 @@ Importar desde archivo Importar desde código QR Importar desde NFC - Exportar todas las claves Exportar a archivo Borrar clave Crear clave @@ -81,6 +78,7 @@ Correo electrónico + + diff --git a/OpenPGP-Keychain/src/main/res/values-es/strings.xml b/OpenPGP-Keychain/src/main/res/values-es/strings.xml index c643a3cd8..55136642c 100644 --- a/OpenPGP-Keychain/src/main/res/values-es/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-es/strings.xml @@ -16,6 +16,7 @@ Cambiar frase de contraseña Establecer frase de contraseña Enviar email... + Enviar archivo... Cifrar hacia archivo Descifrar hacia archivo Importar claves @@ -47,6 +48,7 @@ Certificar Descifrar Descifrar y verificar + Desde el portapapeles Seleccionar destinatarios Cifrar archivo Guardar @@ -54,8 +56,8 @@ Eliminar Ninguno De acuerdo - Cambiar la frase de contraseña - Establecer frase de contraseña + Cambiar nueva frase de contraseña + Establecer nueva frase de contraseña Buscar Cargar al servidor de claves Siguiente @@ -63,6 +65,8 @@ Portapapeles Compartir con... Buscar clave + Mostrar ajustes avanzados + Ocultar ajustes avanzados Ajustes Ayuda @@ -70,16 +74,18 @@ Importar desde código QR Importar Importar desde NFC - Exportar todas las claves + Exportar todas las claves públicas + Exportar todas las claves secretas Exportar hacia archivo Borrar clave Crear clave Crear clave (experto) Buscar - Importar desde servidor de claves + Servidor de claves... + Servidor de claves... Actualizar desde servidor de claves Cargar al servidor de claves - Compartir + Compartir... Compartir la huella digital... Compartir la clave completa... con... @@ -93,6 +99,8 @@ Cancelar Cifrar hacia... Seleccionar todo + Añadir claves + Exportar claves Firmar Mensaje @@ -105,6 +113,7 @@ Destinatarios Borrar después del cifrado Borrar después del descifrado + Compartir después del cifrado Algoritmo de cifrado Algoritmo de Hash Clave pública @@ -134,11 +143,17 @@ <sin nombre> <ninguna> <sin clave> + <No hay un email> se puede cifrar se puede firmar caducado revocado + ID de usuario + + 1 contacto + %d contactos + %d servidor de claves %d servidores de claves @@ -147,9 +162,6 @@ Clave secreta: Ninguna - Solo firmar - Solo cifrar - Firmar y cifrar 15 segs 1 min 3 mins @@ -169,21 +181,26 @@ Advertencia Error Error: %s + + Certificar + Firmar + Cifrar + Autentificar Frase de contraseña incorrecta. Usando el contenido del portapapeles. Establece una frase de contraseña antes. No hay un gestor de archivos compatible instalado. Las frases de contraseña no coinciden. - Las frases de contraseña no pueden estar vacías. + Por favor, introduce una frase de contraseña. Cifrado simétrico. Introducir la frase de contraseña para \'%s\' ¿Estás seguro de que quieres borrar\n%s? Borrado satisfactoriamente. Selecciona un archivo antes. - Descifrado satisfactoriamente. - Cifrado satisfactoriamente. - Cifrado satisfactoriamente al portapapeles. + Descifrado y/o verificado satisfactoriamente. + Firmado y/o cifrado satisfactoriamente. + Firmado y/o cifrado al portapapeles satisfactoriamente. Introduce la frase de contraseña dos veces. Selecciona al menos una clave de cifrado. Selecciona al menos una clave de cifrado o de firma. @@ -194,6 +211,11 @@ ¿Quieres realmente borrar la clave \'%s\'?\n¡No podrás deshacerlo! ¿Quieres realmente borrar todas las claves seleccionadas?\n¡No podrás deshacerlo! ¿Quieres realmente borrar la clave SECRETA \'%s\'?\n¡No podrás deshacerlo! + Has hecho cambios en el almacén de claves, ¿quieres guardarlos? + Has añadido una ID de usuario vacía, ¿Estás seguro que quieres continuar? + ¿Quieres realmente borrar la clave PÚBLICA \'%s\'?\n¡No podrás deshacerlo! + ¿Borrar claves secretas? + ¿Exportar también las claves secretas? %d clave añadida satisfactoriamente %d claves añadidas satisfactoriamente @@ -215,6 +237,7 @@ %d claves exportadas satisfactoriamente. No se han exportado claves. Nota: solo las subclaves son compatibles con ElGamal, y para ElGamal debe usarse el tamaño de clave más próximo de 1536, 2048, 3072, 4096, o 8192. + Nota: generar una clave RSA de longitud 1024-bit o menos está considerado inseguro y desactivado para generar nuevas claves. No se puede encontrar la clave %08X. Se ha encontrado %d clave. @@ -246,6 +269,7 @@ la clave maestra no puede ser una clave ElGamal elegido algoritmo desconocido necesitas determinar un nombre + no se ha encontrado un email tienes que determinar una dirección de email necesitas al menos una ID de usuario la ID del usuario principal no puede estar vacía @@ -266,22 +290,29 @@ ¡NFC no está disponible en tu dispositivo! ¡Nada que importar! la fecha de caducidad debe ser posterior a la fecha de creación + Por favor, guarda el almacén de claves antes no puedes eliminar este contacto porque eres tú mismo. no puedes eliminar los siguientes contactos porque son tú mismo:\n%s Consulta al servidor insuficiente La consulta al servidor de claves ha fallado Demasiadas respuestas + El archivo está vacio + Ha ocurrido un error genérico, por favor, informa de este bug a OpenKeychain Por favor, bórralo desde la pantalla \'Mis claves\'! Por favor, bórralos desde la pantalla \'Mis claves\'! + + parte del archivo cargado es un objeto OpenPGP válido pero no una clave OpenPGP + partes del archivo cargado son objetos OpenPGP válidos pero no claves OpenPGP + + Debes hacer cambios en el almacén de claves antes de que puedas guardarlo - hecho. - cancelar + Hecho. + Cancelar guardando... importando... exportando... - generando la clave, esto puede tardar más de 3 minutos... construyendo la clave... preparando la clave maestra... certificando la clave maestra... @@ -292,6 +323,10 @@ exportando clave... exportando claves... + + generando clave, esto puede tardar más de 3 minutos... + generando claves, esto puede tardar más de 3 minutos... + extrayendo la clave de firma... extrayendo la clave... preparando las transmisiones... @@ -322,6 +357,7 @@ muy lento Comenzar + FAQ NFC Beam Registro de cambios A cerca de @@ -349,15 +385,22 @@ Descifrar con OpenKeychain ¡No hay aplicaciones registradas!\n\nLas aplicaciones de terceros pueden pedir permiso de acceso a OpenKeychain. Después de obtener acceso, serán enumeradas aquí. + Mostrar información avanzada + Ocultar información avanzada Mostrar la configuración avanzada Ocultar la configuración avanzada No se ha seleccionado ninguna clave Seleccionar clave + Crear una nueva clave para esta cuenta Guardar Cancelar Revocar acceso + Borrar cuenta Nombre de paquete SHA-256 de firma de paquete + Cuentas + No hay cuentas asociadas a esta aplicación. + La aplicación solicita la creación de una nueva cuenta. Por favor, selecciona una clave privada que ya exista o crea una nueva.\n¡Las aplicaciones tienen restringido el uso de claves a las que tú selecciones aquí! La aplicación mostrada solicita acceso a OpenKeychain.\n¿Permitir el acceso?\n\nAVISO: Si no sabes por qué aparece esta pantalla, ¡deniega el acceso! Puedes revocarlo después usando la pantalla \'Aplicaciones registradas\'. Permitir el acceso Denegar el acceso @@ -383,17 +426,28 @@ crear tu propia clave importar claves + Editar esta clave Cifrar hacia este contacto Certificar la clave de este contacto Información Certificaciones - Contactos - Cifrar - Descifrar + Claves + Firmar y cifrar + Descifrar y verificar Importar claves Mis claves Aplicaciones registradas Abrir el Navigation Drawer Cerrar el Navigation Drawer + Editar + Mis claves + Claves secretas + disponible + no disponible + IDs de usuario para firmar + Nueva aplicación de certificados + + Escribe aquí el mensaje que quieras cifrar y/o firmar... + Introduce aquí el texto cifrado para descifrarlo y/o verificarlo... diff --git a/OpenPGP-Keychain/src/main/res/values-et/strings.xml b/OpenPGP-Keychain/src/main/res/values-et/strings.xml new file mode 100644 index 000000000..62741da09 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/values-et/strings.xml @@ -0,0 +1,119 @@ + + + + Kontaktid + Salajased võtmed + Vali avalik võti + Vali salajane võti + Krüpteeri + Dekrüpteeri + Salasõne + Loo võti + Muuda võtit + Seaded + Registreeritud rakendused + Võtmeserveri seaded + Määra salasõne + Saada kiri... + Impordi võtmeid + Ekspordi võti + Ekspordi võtmed + Võtit ei leitud + Päri võtmeserverist + Lae võtmeserverisse + Võõras allkirjastamise võti + Abi + + Kasutaja ID-d + Võtmed + Üldine + Vaikeseaded + + Allkirjasta + Dekrüpteeri + Vali saajad + Salvesta + Katkesta + Kustuta + Otsi + Saada võtmeserverisse + Järgmine + Tagasi + + Seaded + Kustuta võti + Loo võti + Otsi + Võtmeserver... + Uuenda võtmeserverist + Saada võtmeserverisse + Jaga... + Allkirjasta võti + + Allkirjasta + Sõnum + Fail + Salasõnet pole + Salasõne + Uuesti + Algoritm + Saajad + Kustuta peale šifreerimist + Räsialgoritm + Avalik võti + Salasõne + Salasõne puhverdus + Võtmeserverid + Loodud + Aegub + Kasutusvaldkond + Võtmepikkus + Nimi + Kommentaar + E-mail + + aegunud + Sõrmejälg: + Salajane võti: + + 15 sekundit + 1 minut + 3 minutit + 5 minutit + 10 minutit + 20 minutit + 40 minutit + 1 tund + 2 tundi + 4 tundi + 8 tundi + DSA + ElGamal + RSA + Ava... + Hoiatus + Viga + Viga: %s + + + Vale salasõne + Määra enne salasõne. + Salasõned ei ühti. + Sümmeetriline krüpteering + + + + + + + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/values-fa-rIR/strings.xml b/OpenPGP-Keychain/src/main/res/values-fa-rIR/strings.xml index 6bb115049..7b71d3ecf 100644 --- a/OpenPGP-Keychain/src/main/res/values-fa-rIR/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-fa-rIR/strings.xml @@ -7,6 +7,7 @@ + + diff --git a/OpenPGP-Keychain/src/main/res/values-fr/strings.xml b/OpenPGP-Keychain/src/main/res/values-fr/strings.xml index d99bbcd7c..e7d8b0b7f 100644 --- a/OpenPGP-Keychain/src/main/res/values-fr/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-fr/strings.xml @@ -48,6 +48,7 @@ Certifier Déchiffrer Déchiffrer et vérifier + À partir du presse-papiers Choisir les destinataires Chiffrer le fichier Enregistrer @@ -55,8 +56,8 @@ Supprimer Aucune OK - Changer la phrase de passe - Définir la phrase de passe + Changer la nouvelle phrase de passe + Définir la nouvelle phrase de passe Rechercher Téléverser vers le serveur de clefs Suivant @@ -64,6 +65,8 @@ Presse-papiers Partager avec... Rechercher la clef + Afficher les paramètres avancés + Masquer les paramètres avancés Paramètres Aide @@ -71,16 +74,18 @@ Importer depuis un code QR Importer Importer avec NFC - Exporter toutes les clefs + Exporter toutes les clefs publiques + Exporter toutes les clefs secrètes Exporter vers un fichier Supprimer la clef Créer une clef Créer une clef (expert) Rechercher - Importer depuis le serveur de clefs + Serveur de clefs + Serveur de clefs... Mettre à jour depuis le serveur de clefs Téléverser vers le serveur de clefs - Partager + Partager... Partager l\'empreinte... Partager la clef entière... avec... @@ -94,6 +99,8 @@ Annuler Chiffrer vers... Tout sélectionner + Ajouter des clefs + Exporter des clefs Signer Message @@ -125,8 +132,6 @@ Nom Commentaire Courriel - Signer l\'ID utilisateur - Signer le courriel Téléverser la clef vers le serveur de clefs choisi après certification Empreinte Choisir @@ -138,12 +143,17 @@ <aucun nom> <aucune> <pas de clef> + <aucun courriel> peut chiffrer peut signer expiré révoquée ID utilisateur + + 1 contact + %d contacts + %d serveur de clefs %d serveurs de clefs @@ -152,9 +162,6 @@ Clef secrète : Aucune - Signer seulement - Chiffrer seulement - Signer et chiffrer 15 s 1 min 3 min @@ -174,21 +181,26 @@ Avertissement Erreur Erreur : %s + + Certifier + Signer + Chiffrer + Authentifier Phrase de passe erronée Utiliser le contenu du presse-papiers. Définir d\'abord une phrase de passe. Aucun gestionnaire de fichiers compatible installé. Les phrases de passe ne correspondent pas. - Les phrases de passe vides ne sont pas autorisées. + Veuillez saisir une phrase de passe Chriffrement symétrique. Saisir une phrase de passe pour « %s » Êtes-vous sûr de vouloir supprimer\n%s ? Supprimé avec succès. Choisir d\'abord un fichier. - Déchiffré avec succès. - Chiffré avec succès. - Chiffré vers le presse-papiers avec succès. + Déchiffré et/ou vérifié avec succès + Signé et/ou chiffré avec succès + Signé et/ou chiffré vers le presse-papiers avec succès Saisir la phrase de passe deux fois. Choisir au moins une clef de chiffrement. Choisir au moins une clef de chiffrement ou de signature. @@ -199,6 +211,11 @@ Voulez-vous vraiment supprimer la clef %s ?\nVous ne pourrez pas la restituer ! Voulez-vous vraiment supprimer toutes les clefs choisies ?\nCeci est irréversible ! Voulez-vous vraiment supprimer la clef SECRÈTE %s ?\nVous ne pourrez pas la restituer ! + Vous avez apporté des changements au trousseau, voulez-vous l\'enregistrer ? + Vous avez ajouté un ID utilisateur vide, êtes-vous certain de vouloir continuer? + Voulez-vous vraiment supprimer la clef PUBLIQUE « %s » ?\nVous ne pourrez pas la restituer ! + Supprimer les clefs privées ? + Exporter aussi les clefs secrètes? %d clef ajoutée avec succès %d clefs ajoutées avec succès @@ -220,6 +237,7 @@ %d clefs exportées avec succès. Aucune clef exportée. Note : seules les sous-clefs prennent en charge ElGamal, et pour ElGamal la taille de clef la plus proche de 1 536, 2 048, 3 072, 4 096 ou 8 192 sera utilisée. + Note : générer des clefs RSA d\'une longueur de 1024 bits ou moins est considéré non sécuritaire et est désactivé pour la génération de nouvelles clefs. Clef %08X introuvable. %d clef trouvée. @@ -251,6 +269,7 @@ la clef maîtresse ne peut être une clef ElGama choix d\'algorhitme inconnu vous devez spécifier un nom + aucun courriel trouvé vous devez spécifier une adresse courriel vous avez besoin d\'au moins un ID utilisateur l\'ID utilisateur principal ne doit pas être vide @@ -271,6 +290,7 @@ NFC n\'est pas disponible sur votre appareil ! Rien à importer ! la date d\'expiration doit venir après la date de création + veuillez d\'abord enregistrer le trousseau vous ne pouvez pas supprimer ce contact car c\'est le vôtre. vous ne pouvez pas supprimer les contacts suivants car c\'est les vôtres.\n%s Requête serveur insuffisante @@ -286,13 +306,13 @@ une partie du fichier chargé est un objet OpenPGP valide mais pas une clef OpenPGP certaines parties du fichier chargé sont des objets OpenPGP valides mais pas des clefs OpenPGP + Vous devez apporter des changements au trousseau avant de pouvoir l\'enregistrer - fait. - annuler + Terminé. + Annuler sauvegarde... importation... exportation... - génération de la clef, ceci peut prendre jusqu\'à 3 minutes... assemblage de la clef... préparation de la clef maîtresse... certification de la clef maîtresse... @@ -303,6 +323,10 @@ exportation de la clef... exportation des clefs... + + génération de la clef, ceci peut prendre jusqu\'à 3 min... + génération des clefs, ceci peut prendre jusqu\'à 3 min... + extraction de la clef de signature... extraction de la clef... préparation des flux... @@ -333,6 +357,7 @@ très lent Commencer + FAQ NFC Beam Journal des changements À propos de @@ -360,15 +385,22 @@ Déchiffrer avec OpenKeychain Aucune application enregistrée !\n\nLes applications tierces peuvent demander l\'accès à OpenKeychain. Après avoir autorisé l\'accès, elles seront listées ici. + Afficher les informations avancées + Masquer les informations avancées Afficher les paramètres avancés Masquer les paramètres avancés Aucune clef choisie Choisir une clef + Créer une nouvelle clef pour ce compte Enregistrer Annuler Révoquer l\'accès + Supprimer le compte Nom du paquet SHA-256 de la signature du paquet + Comptes + Aucun compte n\'est attaché à cette application. + L\'application demande la création d\'un nouveau compte. Veuillez choisir un clef privée existante ou en créer une.\nLes applications sont restreintes à l\'utilisation de clefs choisies ici. L\'application affichée demande l\'accès à OpenKeychain.\nPermettre l\'accès ?\n\nAvertissement : si vous ne savez pas pourquoi cet écran est apparu, refusez l\'accès ! Vous pourrez révoquer l\'accès plus tard en utilisant l\'écran « Applications enregistrées ». Permettre l\'accès Enlever l\'accès @@ -394,17 +426,28 @@ créer votre propre clef Importer des clefs. + Modifier cette clef Chiffrer vers ce contact Certifier la clef de ce contact Infos Certifications - Contacts - Chiffrer - Déchiffrer + Clefs + Signer et chiffrer + Déchiffrer et vérifier Importer les clefs Mes clefs Applis enregistrées Ouvrir le tiroir de navigation Fermer le tiroir de navigation + Modifier + Mes clefs + Clef secrète + disponible + non disponible + ID utilisateur pour signer + Nouvel application des certificats + + Écrire ici le message à chiffrer et/ou signer... + Saisir le cryptogramme à déchiffrer et/ou à vérifier ici... diff --git a/OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml b/OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml index f9e7074da..ff46c9606 100644 --- a/OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-it-rIT/strings.xml @@ -48,6 +48,7 @@ Certifica Decodifica Decodifica e Verifica + Dagli Appunti Seleziona Destinatari Codifica File Salva @@ -55,8 +56,8 @@ Elimina Nessuno Okay - Cambia Frase Di Accesso - Imposta Frase di Accesso + Cambia Nuova Frase di Accesso + Imposta Nuova Frase di Accesso Cerca Carica sul Server delle Chiavi Prossimo @@ -64,6 +65,8 @@ Appunti Condividi con... Chiave di ricerca + Mostra impostazioni avanzate + Nascondi impostazioni avanzate Impostazioni Aiuto @@ -71,16 +74,17 @@ Importa da Codice QR Importa Importa tramite NFC - Esporta tutte le chiavi + Esporta tutte le chiavi segrete Esporta su un file Cancella chiave Crea chiave - Crea chiave (esperto) + Crea chiave (avanzato) Cerca - Importa dal server delle chiavi + Server delle Chiavi + Server delle Chiavi... Aggiorna dal server delle chiavi Carica chiave nel server - Condividi + Condividi... Condivi impronta... Condividi intera chiave... con.. @@ -100,7 +104,7 @@ File Nessuna Frase di Accesso Frase di Accesso - Ancora + Di nuovo Algortimo Armatura ASCII Destinatari @@ -125,8 +129,6 @@ Nome Commento Email - Firma ID Utente - Firma email Carica chiave nel server delle chiavi selezionati dopo la certificazione Impronta Seleziona @@ -138,12 +140,17 @@ <nessun nome> <nessuno> <nessuna chiave> + <Nessuna Email> puo\'; codificare puo\' firmare scaduto revocato ID Utente + + 1 contatto + %d contatti + %d server delle chiavi %d server delle chiavi @@ -152,9 +159,6 @@ Chiave Privata: Nessuno - Firma soltanto - Codifica soltanto - Firma e Codifica 15 sec 1 min 3 min @@ -174,21 +178,22 @@ Attenzione Errore Errore: %s + + Certifica + Firma + Codifica + Convalida Frase di Accesso errata Utilizzo il contenuto degli appunti. Imposta prima una frase di accesso. Nessun gestore file compatibile installato. Le frasi di accesso non corrispondono. - Frasi di accesso vuote non consentite. Codifica Simmetrica. Inserisci la frase di accesso per \'%s\' Sei sicuro di voler cancellare\n%s? Eliminato correttamente. Seleziona un file prima. - Decodificato correttamente. - Codificato correttamente. - Codificato correttamente negli appunti. Inserisci la frase di accesso due volte. Seleziona almeno una chiave di codifica. Seleziona almeno una chiave di codifica o di firma. @@ -199,6 +204,10 @@ Vuoi veramente eliminare la chiave \'%s\'?\nNon potrai annullare! Vuoi veramente eliminare le chiavi selezionate?\nNon potrai annullare! Vuoi veramente eliminare la chiave PRIVATA \'%s\'?\nNon potrai annullare! + Hai apportato modifiche al tuo portachiavi, vuoi salvarlo? + Vuoi veramente eliminare la chiave PUBBLICA \'%s\'?\nNon potrai annullare! + Eliminare le Chiavi Segrete? + Esportare anche le chiavi segrete? %d chiave aggiunta correttamente %d chiavi aggiunte correttamente @@ -220,6 +229,7 @@ %d chiavi esportate correttamente. Nessuna chiave esportata. Nota: solo le sottochiavi supportano ElGamal, e per ElGamal verra\' usata la grandezza chiave piu\' vicina a 1536, 2048, 3072, 4096 o 8192. + Nota: la generazione di chiavi RSA con lunghezza pari a 1024 bit o inferiore è considerata non sicura ed è disabilitata per la generazione di nuove chiavi. Impossibile trovare la chiave %08X. Trovata %d chiave. @@ -251,6 +261,7 @@ La chiave principale non puo\' essere ElGamal opzione algoritmo sconosciuta devi specificare un nome + Nessuna email trovata devi specificare un indirizzo email necessario almeno un id utente id utente principale non puo\' essere vuoto @@ -271,6 +282,7 @@ NFC non disponibile nel tuo dispositivo! Niente da importare! La data di scadenza deve essere postuma quella di creazione + si prega di salvare il portachiavi primo Non è possibile eliminare questo contatto, perché è il proprio. Non è possibile eliminare i seguenti contatti perché sono i propri:\n%s Query di server insufficiente @@ -287,12 +299,11 @@ parti del file caricato sono oggetti OpenPGP validi, ma non chavi OpenPGP - fatto. - cancella + Fatto. + Annulla salvataggio... importazione... esportazione... - generazione chiave, richiede fino a 3 minuti... fabbricazione chiave... preparazione chiave principale... certificazione chiave principale... @@ -303,6 +314,10 @@ esportazione chiave... esportazione chiavi... + + generazione chiave, sono necessari fino a 3 minuti... + generazione chiavi, sono necessari fino a 3 minuti... + estrazione chiavi di firma... estrazione chiave... preparazione flussi... @@ -333,6 +348,7 @@ molto lento Inizia + FAQ NFC Beam Novita\' Info @@ -359,16 +375,23 @@ Codifica con OpenKeychain Decodifica con OpenKeychain - Nessuna app registrata!\n\nApp di terza parti possono richiedere accesso a OpenKeychain. Dopo aver concesso l\'accesso, saranno elencate qui. + Nessuna app registrata!\n\nApp di terze parti possono richiedere l\'accesso a OpenKeychain. Dopo aver concesso l\'accesso, le app saranno elencate qui. + Mostra informazioni dettagliate + Nascondi informazioni dettagliate Mostra impostazioni avanzate Nascondi impostazioni avanzate Nessuna chiave selezionata Seleziona chiave + Crea una nuova chiave per questo account Salva Annulla Revoca accesso + Cancella account Nome Pacchetto SHA-256 della Firma del Pacchetto + Account + Nessun account collegato a questa applicazione + L\'applicazione richiede la creazione di un nuovo account. Si prega di selezionare una chiave privata esistente o crearne una nuova.\nLe applicazioni sono limitate all\'utilizzo delle chiavi selezionate qui! Le app visualizzate hanno richiesto l\'accesso a OpenKeychain.\nPermetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' apparsa, nega l\'accesso! Puoi revocare l\'accesso dopo, usando la schermata \'App Registrate\'. Permetti accesso Nega accesso @@ -394,17 +417,23 @@ creazione della tua chiave importazione chiavi. + Modifica chiave Codifica a questo contatto Certifica la chiave di questo contatto Info Certificazioni - Contatti - Codifica - Decodifica - Importare Chiavi + Importa Chiavi Le Mie Chiavi App Registrate Apri drawer di navigazione Chiudi drawer di navigazione + Modifica + Le Mie Chiavi + Chiave Segreta + disponibile + non disponibile + ID Utente da firmare + Riapplicazione certificati + diff --git a/OpenPGP-Keychain/src/main/res/values-ja/strings.xml b/OpenPGP-Keychain/src/main/res/values-ja/strings.xml index e5ee5ecc0..eceefb8a3 100644 --- a/OpenPGP-Keychain/src/main/res/values-ja/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-ja/strings.xml @@ -41,13 +41,14 @@ あなたの鍵を証明に利用します 鍵のアップロード 鍵サーバ - 暗号化と/もしくは署名 + 暗号化/署名 復号化と検証 署名 検証 復号化 復号化と検証 + クリップボードから 受信者の選択 ファイル暗号化 保存 @@ -55,15 +56,17 @@ 削除 無し OK - パスフレーズの変更 - パスフレーズの設定 + 新しいパスフレーズに変更 + 新しいパスフレーズを設定 検索 鍵サーバへアップロード 戻る クリップボード - 共有... + ...で共有 鍵検出 + 拡張設定を表示 + 拡張設定を隠す 設定 ヘルプ @@ -71,16 +74,18 @@ QRコードからインポート インポート NFCからインポート - すべての鍵のエクスポート + すべての公開鍵のエクスポート + すべての秘密鍵のエクスポート ファイルへのエクスポート 鍵の削除 鍵の生成 鍵の生成(上級) 検索 - 鍵サーバからのインポート + 鍵サーバ + 鍵サーバ... 鍵サーバからの更新 鍵サーバへのアップロード - 共有 + 共有... 指紋の共有... すべての鍵の共有... ...(指紋) @@ -94,6 +99,8 @@ キャンセル 暗号化... すべて選択 + 鍵の追加 + 複数鍵のエクスポート 署名 メッセージ @@ -125,8 +132,6 @@ 名前 コメント Eメールアドレス - 署名ユーザーID - メールを署名 証明後選択した鍵サーバに鍵をアップロード 指紋 選択 @@ -137,12 +142,16 @@ <名前なし> <無し> <鍵無し> + <メールなし> 暗号化可能 署名可能 期限切れ 破棄 ユーザーID + + %d個の連絡先 + %d の鍵サーバ @@ -150,9 +159,6 @@ 秘密鍵: 無し - 署名のみ - 暗号化のみ - 署名と暗号化 15秒 1分 3分 @@ -172,21 +178,26 @@ 注意 エラー エラー: %s + + 検証 + 署名 + 暗号化 + 証明 良くないパスフレーズ クリップボードの内容を使う。 最初にパスフレーズを設定してください。 互換性のないファイルマネージャがインストールされています。 パスフレーズが一致しません。 - 空のパスフレーズは受け付けません。 + パスフレーズを入れてください。 対称暗号。 \'%s\' にパスフレーズを入れてください。 %s を削除してもかまいませんか? 削除に成功しました。 最初にファイルを選択してください。 - 復号化に成功しました。 - 暗号化に成功しました。 - クリップボードの中身の暗号化に成功しました。 + 復号化/検証に成功しました。 + 署名/暗号化に成功しました。 + クリップボードの中身の署名/暗号化に成功しました。 もう一度パスフレーズを入れてください。 少なくとも1つの暗号化鍵を選択して下さい。 少なくとも1つの暗号化鍵か署名鍵を選択して下さい。 @@ -197,6 +208,11 @@ 鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません! 選択したすべての鍵を本当に削除してよいですか?\nこれは元に戻せません。 秘密鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません! + あなたは鍵輪に変更を加えました、これを保存しますか? + あなたは空のユーザーIDを追加しました、このまま続けますか? + 公開鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません! + 秘密鍵を削除しますか? + 秘密鍵もエクスポートしますか? %d の鍵を追加しました @@ -214,6 +230,7 @@ %d の鍵をエクスポートしました。 鍵をエクスポートしていません。 備考: 副鍵として ElGamalだけがサポートされ, ElGamal は鍵サイズとして1536, 2048, 3072, 4096, 8192 だけが使えます。 + 付記: 長さ1024bitかそれ以下で生成されたRSA鍵は安全とはみなされず、新な鍵の生成は無効にされています。 鍵 %08X は見付かりませんでした。 %d の鍵を発見。 @@ -243,6 +260,7 @@ 主鍵を ElGamal にすることはできません 未知のアルゴリズムを選択しています 名前を特定する必要があります + メールが見付かりません Eメールアドレスを特定する必要があります 最低でも1つのユーザIDが必要です 主ユーザIDは空にすることはできません @@ -263,6 +281,7 @@ あなたのデバイスにはNFCが存在しません! インポートするものがありません! 期限日時は生成日時より後である必要があります + まず鍵輪を保存してください この連絡先はあなたなので削除できません。 この連絡先はあなたなので削除できません。:\n%s サーバへのクエリーが不足しています @@ -276,13 +295,13 @@ 読み込んだファイルのOpenPGPオブジェクト部分は正しいですが、OpenPGPの鍵ではありません + あなたは鍵輪を保存する前に変更を加えなくてはなりません 完了。 キャンセル 保存... インポート... エクスポート... - 鍵の生成、3分ほどかかります... 鍵の構築中... 主鍵の準備中... 主鍵の検証中... @@ -292,6 +311,9 @@ 鍵のエクスポート... + + 鍵の生成中、最大3分ほどかかります... + 署名鍵の取り出し中... 鍵の取り出し中... ストリームの準備中... @@ -311,7 +333,7 @@ 公開鍵の検索 秘密鍵の検索 - 鍵の共有... + ...で鍵の共有 512 1024 @@ -322,6 +344,7 @@ とても遅い 開始 + FAQ NFC Beam Changelog これについて @@ -348,15 +371,22 @@ OpenKeychainで復号化 登録されていないアプリケーション!\n\nサードパーティアプリケーションはOpenKeychainにアクセスを要求できます。アクセスを与えた後、それらはここにリストされます。 + 詳細情報を表示 + 詳細情報を非表示 拡張設定を表示 - 拡張設定を非表示 + 拡張設定を隠す 鍵が選択されていない 鍵の選択 + このアカウントで新しい鍵を生成 保存 キャンセル 破棄されたアクセス + アカウントを削除 パッケージ名 パッケージの署名 SHA-256 + アカウント + このアプリケーションに接続されてるアカウントはありません。 + このアプリケーションは新しいアカウントの生成を要求しています。すでにある秘密鍵を選択するか、新しく生成してください。\nここであなたが選択する鍵の使い道についてアプリケーションには制約があります! 表示されているアプリケーションはOpenKeychainへのアクセスを要求しています。\nアクセスを許可しますか?\n\n注意: もしなぜスクリーンに表れたかわからないなら、アクセスを許可しないでください! あなたは\'登録済みアプリケーション\'スクリーンを使って、以降のアクセスを破棄するこもできます。 許可されたアクセス 許可されないアクセス @@ -381,17 +411,28 @@ あなた所有の鍵を作る 鍵のインポート。 + この鍵の編集 この連絡先を暗号化 この連絡先の鍵を検証 情報 証明 - 連絡先 - 暗号化 - 復号化 + + 署名と暗号化 + 復号化と検証 鍵のインポート 自分の鍵 登録済みのアプリ ナビゲーションドロワーを開く ナビゲーションドロワーを閉める + 編集 + 自分の鍵 + 秘密鍵 + 存在する + 存在しない + 署名に使うユーザーID + 検証を再適用する + + ここに書いたメッセージを暗号化/署名.. + ここに入力された暗号化テキストを復号化/検証... diff --git a/OpenPGP-Keychain/src/main/res/values-large/dimens.xml b/OpenPGP-Keychain/src/main/res/values-large/dimens.xml new file mode 100644 index 000000000..192a4bb99 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/values-large/dimens.xml @@ -0,0 +1,4 @@ + + + 240dp + diff --git a/OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml b/OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml index de6ba554d..9cb9a4756 100644 --- a/OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-nl-rNL/strings.xml @@ -36,8 +36,6 @@ Verwijderen Geen OK - Wachtwoord wijzigen - Wachtwoord instellen Zoeken Volgende Terug @@ -46,7 +44,6 @@ Importeren uit bestand Importeren met QR-code Importeren met NFC - Alle sleutels exporteren Exporteren naar bestand Sleutel verwijderen Sleutel aanmaken @@ -91,9 +88,6 @@ Privésleutel: Geen - Alleen ondertekenen - Alleen versleutelen - Ondertekenen en versleutelen 15 sec. 1 min. 3 min. @@ -112,21 +106,18 @@ Waarschuwing Fout Fout: %s + Wachtwoord verkeerd. Gebruikmaken van klembordinhoud. Stel eerst een wachtwoord in. Geen compatibele bestandsbeheerder geïnstalleerd. De wachtwoorden komen niet overeen. - Lege wachtwoorden zijn niet toegestaand. Symmetrische versleuteling. Voer het wachtwoord in voor \'%s\' Weer u zeker dat u het volgende wilt verwijderen:\n%s? Succesvol verwijderd. Selecteer eerst een bestand. - Succesvol ontsleuteld. - Succesvol versleuteld. - Succesvol versleuteld naar klembord. Voer het wachtwoord tweemaal in. Selecteer ten minste één versleutelingssleutel. Selecter ten minste één versleutelings-/ondertekeningssleutel. @@ -170,7 +161,6 @@ Uw apparaat biedt geen ondersteuning voor NFC Niets te importeren - gereed. opslaan... importeren... exporteren... @@ -236,4 +226,5 @@ + diff --git a/OpenPGP-Keychain/src/main/res/values-pl/strings.xml b/OpenPGP-Keychain/src/main/res/values-pl/strings.xml new file mode 100644 index 000000000..336f0bff7 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/values-pl/strings.xml @@ -0,0 +1,468 @@ + + + + Kontakty + Klucze prywatne + Wybierz Klucz Publiczny + Wybierz Klucz Prywatny + Zaszyfruj + Odszyfruj + Hasło + Utwórz Klucz + Edytuj Klucz + Właściwości + Zarejestrowane Aplikacje + Właściwości serwera kluczy + Zmień hasło + Ustaw hasło + Wyślij maila... + Wyślij plik... + Zaszyfruj do pliku + Odszyfruj do pliku + Importuj klucze + Eksportuj klucz + Eksportuj klucze + Nie znaleziono klucza + Wyślij zapytanie do serwera kluczy + Wyślij do serwera kluczy + Nieznany klucz podpisu + Certyfikuj klucz + Szczegóły klucza + Pomoc + + Identyfikator użytkownika + Klucze + Ogólne + Domyślne + Zaawansowane + Klucz główny + Główny identyfikator użytkownika + Działania + Twój klucz użyty do certyfikacji + Wyślij klucz + Serwer kluczy + Zaszyfruj i/lub podpisz + Deszyfruj i weryfikuj + + Podpisz + Certyfikuj + Odszyfruj + Deszyfruj i weryfikuj + Ze schowka + Wybierz odbiorców + Zaszyfruj plik + Zapisz + Anuluj + Usuń + Żaden + Ok + Zmień nowe hasło + Ustaw nowe hasło + Wyszukaj + Wyślij do serwera kluczy + Dalej + Wstecz + Schowek + Podziel się z... + Klucz wyszukiwania + Pokaż zaawanowane ustawienia + Ukryj zaawansowane ustawienia + + Ustawienia + Pomoc + Zaimportuj z pliku + Zaimportuj z kodu QR + Import + Zaimportuj przy użyciu NFC + Eksportuj wszystkie klucze publiczne + Eksportuj wszystkie prywatne klucze + Eksportuj do pliku + Usuń klucz + Stwórz klucz + Stwórz klucz (tryb zaawansowany) + Znajdź + Serwer kluczy + Serwer kluczy... + Aktualizuj z serwera kluczy + Wyślij do serwera kluczy + Udostepnij... + Udostepnij odcisk... + Udostępnij cały klucz... + z... + z... + za pomocą kodu QR + za pomocą kodu QR + za pomocą NFC + Kopiuj do schowka + Klucz podpisu + Ustawienia Beam + Anuluj + Zaszyfruj do... + Wybierz wszystko + Dodaj klucze + Eksportuj klucze + + Podpis + Wiadomość + Plik + Brak hasła + Hasło + Ponów + Algorytm + ASCII Armor + Odbiorcy + Usuń po zaszyfrowaniu + Usuń po odszyfrowaniu + Udostępnij po zaszyfrowaniu + Algorytm szyfrujący + Algorytm funkcji skrótu + Klucz publiczny + Hasło + Bufor haseł + Kompresja wiadomości + Kompresja plików + Wymuś stare podpisy OpenPGPv3 + Serwery kluczy + Identyfikator klucza + Utworzenia + Wygaśnięcia + Wykorzystanie + Rozmiar klucza + Identyfikator głównego użytkownika + Imię + Komentarz + Adres email + Wyślij klucz do serwera kluczy po certyfikacji + Odcisk + Wybierz + Ustaw datę wygaśnięcia + + wybrano %d + wybrano %d + wybrano %d + + <bez nazwy> + <żaden> + <brak klucza> + <Brak adresu email> + + może szyfrować + może podpisywać + wygasły + unieważniony + Identyfikator użytkownika + + 1 kontakt + %d kontakty + %d kontaktów + + + %d serwer kluczy + %d serwerów kluczy + %d serwerów kluczy + + Odcisk: + Klucz prywatny: + + Brak + 15 sekund + 1 minuta + 3 minuty + 5 minut + 10 minut + 20 minut + 40 minut + 1 godzina + 2 godziny + 4 godziny + 8 godzin + na zawsze + DSA + ElGamal + RSA + Otwórz... + Ostrzeżenie + Błąd + Błąd: %s + + Certyfikuj + Podpisz + Zaszyfruj + Autentykuj + + Nieprawidłowe hasło. + Użycie zawartości schowka. + Najpierw ustaw hasło. + Nie zainstalowano żadnego kompatybilnego menadżera plików. + Hasła nie pasują do siebie + Podaj hasło. + Szyfrowanie symetryczne. + Podaj hasło dla \'%s\' + Czy jesteś pewien że chcesz usunąć\n%s? + Usunięto pomyślnie. + Najpierw wskaż plik. + Pomyślnie deszyfrowano i/lub zweryfikowano. + Pomyślnie podpisano i/lub zaszyfrowano. + Pomyslnie podpisano i/lub zaszyfrowano do schowka. + Podaj hasło dwukrotnie. + Wybierz co najmniej jeden klucz szyfrujący. + Wybierz co najmniej jeden klucz szyfrujący lub klucz podpisujący. + Wskaż, do którego pliku zapisać zaszyfrowane dane.\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje. + Wskaż, do którego pliku zapisać odszyfrowane dane.\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje. + Wskaż, do którego pliku wyeksportować dane.\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje. + Wskaż, do którego pliku zapisać eksportowane dane.\nOSTRZEŻENIE: Masz zamiar zapisać klucze PRYWATNE (tajne)\nOSTRZEŻENIE: Plik zostanie nadpisany, jeżeli istnieje. + Czy na pewno chcesz usunąć klucz \'%s\'?\nNie można cofnąć tej operacji! + Czy na pewno chcesz usunąć wszystkie zaznaczone klucze?\nTej operacji nie można cofnąć! + Czy na pewno chcesz usunąć klucz prywatny \'%s\'?\nNie można cofnąć tej operacji! + Zostały dokonane zmiany w pęku kluczy, czy chcesz je zachować? + Dodałeś pusty identyfikator użytkownika, czy na pewno chcesz kontynuować? + Czy na pewno chcesz usunąć klucz publiczny \'%s\'?\nNie można cofnąć tej operacji! + Usunąć klucze prywatne? + Czy wyeksportować również klucze prywatne? + + Pomyślnie dodano %d klucz + Pomyślnie dodano %d kluczy + Pomyślnie dodano %d kluczy + + + i zaktualizowano %d klucz. + i zaktualizowano %d kluczy. + i zaktualizowano %d kluczy. + + + Pomyślnie dodano %d klucz. + Pomyślnie dodano %d kluczy. + Pomyślnie dodano %d kluczy. + + + Pomyślnie zaktualizowano %d klucz. + Pomyślnie zaktualizowano %d kluczy. + Pomyślnie zaktualizowano %d kluczy. + + Nie dodano ani zaktualizowano żadnych kluczy. + Pomyślnie wyeksportowano 1 klucz. + Pomyślnie wyeksportowano %d kluczy. + Nie wyeksportowano żadnych kluczy. + Uwaga: algorytm EnGamal jest obsługiwany tylko przez podklucze i użyty zostanie najbliższy rozmiar klucza z podanych: 1536, 2048, 3072, 4096, 8192. + Uwaga: generowanie klucza RSA o długości 1024 bity i mniejszej jest uważane za niebezpieczne i wyłączone dla tworzenia nowych kluczy. + Nie można znaleźć klucza %08X. + + Znaleziono %d klucz. + Znaleziono %d kluczy. + Znaleziono %d kluczy. + + Nieznany podpis, naciśnij przycisk, aby wyszukać brakujący klucz. + + Zignorowano %d niepoprawny klucz prywatny. Prawdopodobnie został wyeksportowany przy uzyciu opcji\n --export-secret-subkeys\nUpewnij się że eksportujesz go z opcją\n --export-secret-keys\nktóra jest poprawna. + Zignorowano %d niepoprawnych kluczy prywatnych. Prawdopodobnie zostały wyeksportowane przy uzyciu opcji\n --export-secret-subkeys\nUpewnij się że eksportujesz je z opcją\n --export-secret-keys\nktóra jest poprawna. + zignorowano %d niepoprawnych kluczy prywatnych. Prawdopodobnie zostały wyeksportowane przy uzyciu opcji\n --export-secret-subkeys\nUpewnij się że eksportujesz je z opcją\n --export-secret-keys\nktóra jest poprawna. + + Pomyślnie wysłano klucz na serwer + Pomyślnie podpisano klucz + Lista jest pusta! + Pomyślnie wysłano klucz przez NFC! + Klucz został skopiowany do schowka! + Klucz został już wcześniej podpisany! + Wybierz klucz, który zostanie użyty do podpisania! + Klucz ma za duży rozmiar by być udostępniony w ten sposób! + + usuwanie \'%s\' zakończone niepowodzeniem + plik nie znaleziony + nie znaleziono pasującego klucza prywatnego + napotkano nieznany rodzaj szyfrowania + zewnętrzne urządzenie jest niegotowe + nieprawidłowy adres email \'%s\' + klucz musi mieć rozmiar co najmniej 512 bitów + klucz EnGamal nie może być kluczem głównym + wybrano nieznany algorytm + musisz wskazać imię + nie znaleziono adresu email + musisz wskazać adres email + potrzeba co najmniej jednego identyfikatora użytkownika + główny identyfikator użytkownika nie może być pusty + potrzeba co najmniej klucza głównego + nie podano hasła ani klucza szyfrującego + podpisywanie nie powiodło się + nie podano hasła + nie podano klucza podpisu + nieprawidłowe dane + uszkodzone dane + Sprawdzanie spójności zakończone niepowodzeniem! Dane były modyfikowane! + nie znaleziono pakietu z szyfrowaniem symatrycznym + nieprawidłowe hasło + błąd przy zapisywaniu kluczy + nie można wyodrębnić klucza prywatnego + Dane binarne pozbawione pliku nie są obsługiwane. To jest wspierane tylko dla akcji ACTION_ENCRYPT_STREAM_AND_RETURN. + Potrzebujesz Androida 4.1 Jelly Bean, aby korzystać z Android NFC Beam! + NCF jest niedostępne na twoim urządzeniu + Nie ma nic do zaimportowania! + data wygaśnięcia musi być późniejsza niż data stworzenia + zapisz najpierw pęk kluczy + nie możesz usunąć tego kontaktu, ponieważ należy do ciebie. + nie możesz usunąć tych kontaktów, ponieważ należą do ciebie:\n%s + Niewystarczające zapytanie do serwera + Odpytywanie serwera zakończone niepowodzeniem + Za dużo odpowiedzi + Plik jest pusty + Wystąpił błąd ogólny, proszę zgłoś go autorom OpenKeychain. + + Usuń go z ekranu \'Moje klucze\'! + Usuń je z ekranu \'Moje klucze\'! + Usuń je z ekranu \'Moje klucze\'! + + + Część wczytanego pliku jest poprawnym obiektem OpenPGP, ale nie jest kluczem OpenPGP + Część wczytanego pliku to poprawne obiekty OpenPGP, ale nie są kluczami OpenPGP + Część wczytanego pliku to poprawne obiekty OpenPGP, ale nie są kluczami OpenPGP + + Musisz dokonać zmian w pęku kluczy zanim będziesz mógł go zachować + + Gotowe. + Anuluj + zapisywanie... + importowanie... + eksportowanie... + budowanie klucza... + przygotowywanie klucza glównego... + podpisywanie klucza głównego... + budowanie głównego zbioru kluczy... + dodawanie podkluczy... + zapisywanie klucza... + + eksportowanie klucza... + eksportowanie kluczy... + eksportowanie kluczy... + + + generowanie klucza, może to potrwać do 3 minut... + generowanie kluczy, może to potrwać do 3 minut... + generowanie kluczy, może to potrwać do 3 minut... + + wyodrębnianie klucza podpisu... + wyodrębnianie klucza... + przygotowywanie strumieni... + szyfrowanie danych... + deszyfrowywanie danych... + przygotowywanie podpisu... + generowanie podpisu... + przetwarzanie podpisu... + weryfikowanie podpisu... + podpisywanie... + czytanie danych... + szukanie klucza... + dekompresja danych... + weryfikacja spójności... + usuwanie \'%s\' bezpiecznie… + odpytywanie... + + Wyszukaj klucze publiczne + Wyszukaj klucze prywatne + Udostępnij klucz... + + 512 + 1024 + 2048 + 4096 + + szybka + bardzo wolna + + Początek + FAQ + NFC Beam + Dziennik zmian + O programie + Wersja: + + Zaimportuj wybrane klucze + Importuj, podpisz i wyślij wybrane klucze + Importuj ze schowka + + Brakuje kodu QR o identyfikatorze %s + Brakuje kodów QR o identyfikatorach %s + Brakuje kodów QR o identyfikatorach %s + + Zacznij od kodu QR o identyfikatorze 1 + Kod QR zniekształcony! Spróbuj jeszcze raz! + Skanowanie kodu QR zakończone! + Odcisk klucza zawarty w tym kodzie QR jest za krótki (< 16 znaków) + Odczytaj kod QR przy pomocy \'Barcode Scanner\' + Aby odbierać klucze przez NFC, urządzenie musi być odblokowane. + Pomoc + Odczytaj klucz ze schowka + + Deszyfruj plik korzystając z OpenKeychain + Importuj klucz korzystając z OpenKeychain + Zaszyfruj korzystając z OpenKeychain + Deszyfruj korzystając z OpenKeychain + + Brak zarejestrowanych aplikacji!\n\nZewnętrzne aplikacje mogą żądać dostępu do OpenKeychain. Po przyznaniu dostępu, będa wyświetlone tutaj. + Pokaż zaawansowane informacje + Ukryj zaawansowane informacje + Pokaż zaawanowane ustawienia + Ukryj zaawansowane ustawienia + Nie wybrano klucza + Wybierz klucz + Utwórz nowy klucz dla tego konta + Zapisz + Anuluj + Odwołaj dostęp + Usuń konto + Nazwa paczki + Skrót SHA-256 podpisu paczki + Konta + Nie przypisano żadnych kont do tej aplikacji + Aplikacja prosi o zgodę na utworzenie nowego konta. Wskaż istniejący klucz prywatny lub wygeneruj nowy.\nAplikacje mogą używać wyłącznie klucze które tutaj wskażesz! + Wyświetlona aplikacja prosi o dostęp do OpenKeychain.\nZezwolić?\n\nOSTRZEZENIE: Jeżeli nie wiesz, czemu wyświetlił się ten komunikat, nie zezwalaj na dostęp! Możesz to również zrobić później, korzystając z ekranu \'Zarejestrowane aplikacje\'. + Zezwól na dostęp + Odmów dostępu + Wybierz klucz! + Nie znaleziono kluczy publiczych dla tych identyfikatorów użytkownika: + Więcej niż jeden klucz publiczny istnieje dla tych identyfikatorów użytkownika: + Proszę przejrzeć listę adresatów! + Sprawdzanie podpisu zakończone niepowodzeniem! Czy zainstalowałeś tę aplikację z innego źródła? Jeżeli jesteś pewien, że nie jest to atak, odwołaj rejestrację teg aplikacji w OpenKeychain, a następnie zarejestruj ją ponownie. + + Udostępnij przez kod QR + Przejdź przez wszystkiego kody QR korzystając z przycisku \'Nastepny\' i skanuj je pojedynczo. + Odcisk: + Kod QR o identyfikatorze %1$d z %2$d + Udostępnij przez NFC + + + 1 klucz wybrany. + %d kluczy wybranych. + %d kluczy wybranych. + + Żadne klucze nie są jeszcze dostępne... + Możesz zacząć od + lub + tworzenie własnego klucza + importowanie kluczy. + + Edytuj ten klucz + Zaszyfruj do tego kontaktu + Certyfikuj klucz tego kontaktu + Informacje + Certyfikaty + + Klucze + Podpisz i zaszyfruj + Deszyfruj i weryfikuj + Importuj klucze + Moje klucze + Zarejestrowane aplikacje + Otwórz panel nawigacji + Zamknij panel nawigacji + Edytuj + Moje klucze + Klucz prywatny + dostępny + niedostepny + Identyfikator użytkownika do podpisu + Ponowne stosowanie certyfikatów + + Wpisz tutaj wiadomość do zaszyfrowania i/lub podpisania... + Wpisz tutaj tekst do zaszyfrowania i/lub zweryfikowania... + diff --git a/OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml b/OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml index 6bb115049..7b71d3ecf 100644 --- a/OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-pt-rBR/strings.xml @@ -7,6 +7,7 @@ + + diff --git a/OpenPGP-Keychain/src/main/res/values-ru/strings.xml b/OpenPGP-Keychain/src/main/res/values-ru/strings.xml index 22f676ccb..8ee6d95ca 100644 --- a/OpenPGP-Keychain/src/main/res/values-ru/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-ru/strings.xml @@ -16,6 +16,7 @@ Изменить пароль Задать пароль Отправить... + Отправить файл Зашифровать в файл Расшифровать в файл Импорт ключей @@ -47,6 +48,7 @@ Сертифицировать Расшифровать Расшифровать и проверить + Из буфера обмена Выбрать получателей Зашифровать файл Сохранить @@ -54,8 +56,8 @@ Удалить Нет Да - Изменить пароль - Задать пароль + Изменить новый пароль + Задать новый пароль Поиск Загрузить на сервер ключей Далее @@ -63,6 +65,8 @@ Буфер обмена Поделиться... Найти ключ + Показать расширенные настройки + Скрыть расширенные настройки Настройки Помощь @@ -70,13 +74,15 @@ Импорт из QR кода Импорт Импорт из NFC - Экспорт всех ключей + Экспорт всех открытых ключей + Экспорт всех секретных ключей Экспорт в файл Удалить ключ Создать ключ Создать ключ (эксперт) Поиск - Импорт с сервера ключей + Сервер ключей + Сервер ключей... Обновить с сервера ключей Загрузить на сервер ключей Отправить... @@ -93,6 +99,8 @@ Отмена Зашифровать.... Выбрать все + Добавить ключи + Экспорт ключей Подписать Сообщение @@ -105,6 +113,7 @@ Получатели Удалить после шифрования Удалить после расшифровки + Отправить после шифрования Алгоритм шифрования Hash-алгоритм Публичный ключ @@ -135,11 +144,18 @@ <нет имени> <нет> <нет ключа> + <нет email> шифрование подпись просрочен отозван + ID пользователя + + 1 контакт + %d контактов + %d контактов + %d сервер ключей %d серверов ключей @@ -149,9 +165,6 @@ Секретный ключ: Нет - Только подпись - Только шифрование - Шифрование и подпись 15 секунд 1 минуту 3 минуты @@ -171,21 +184,25 @@ Внимание Ошибка Ошибка: %s + + Сертифицировать + Подписать + Зашифровать Неправ. пароль Следить за буфером обмена Сначала задайте пароль Нет совместимого менеджера файлов. Пароли не совпадают. - Пустой пароль недопустим. + Пожалуйста, введите пароль. Симметричное шифрование. Введите пароль для\n\'%s\' Вы уверены, что хотите удалить\n%s ? Удалено. Сначала выберите файл. - Расшифровано. - Зашифровано. - Зашифровано в буфер обмена. + Расшифровано и/или проверено. + Подписано и/или зашифровано. + Подписано и/или зашифровано в буфер обмена. Дважды введите пароль. Укажите хотя бы один ключ. Выберите хотя бы один ключ для шифрования или подписи. @@ -196,6 +213,9 @@ Вы уверены, что ходите удалить ключ \'%s\'?\nЭто действие нельзя отменить! Вы уверены, что хотите удалить ВСЕ выбранные ключи?\nЭто действие нельзя отменить! Вы уверены, что ходите удалить СЕКРЕТНЫЙ ключ \'%s\'?\nЭто действие нельзя отменить! + Вы правда хотите удалить ПУБЛИЧНЫЙ ключ \'%s\'?\nЭто нельзя отменить! + Удалить секретные ключи? + Экспортировать секретные ключи? Успешно добавлено %d ключ Успешно добавлено %d ключей @@ -221,6 +241,7 @@ Экспортировано %d ключей. Ключи не были экспортированы. Инфо: ElGamal подходит только для дополнительных ключей. При создании ключа будет использован ближайший из размеров: 1536, 2048, 3072, 4096, или 8192. + Внимание: создание ключей RSA длиной 1024 бита и менее признано небезопасным. Данная возможность отключена. Не удается найти ключ %08X. Найден %d ключ. @@ -254,6 +275,7 @@ ключ ElGamal не может быть основным выбран неизвестный алгоритм необходимо указать имя + email не найден необходимо указать email необходим хотя бы один id пользователя основная запись пользователя не может быть пустой @@ -279,18 +301,24 @@ Ограничение запроса сервера Сбой запроса сервера ключей Слишком много ответов + Файл пуст + Выявлена ошибка. Пожалуйста, сообщите о ней разработчику. Пожалуйста, удалите его в разделе \'Мои ключи\'! Пожалуйста, удалите их в разделе \'Мои ключи\'! Пожалуйста, удалите их в разделе \'Мои ключи\'! + + часть загруженного файла содержит данные OpenPGP, но это не ключ + части загруженного файла содержат данные OpenPGP, но это не ключ + части загруженного файла содержат данные OpenPGP, но это не ключ + - готово. - отмена + Готово. + Отмена сохранение... импорт... экспорт... - создание ключа. это может занять до 3 минут... создание ключа... подготовка основного ключа... сертификация основного ключа... @@ -302,6 +330,11 @@ экспорт ключей... экспорт ключей... + + создание ключа. это может занять до 3 минут... + создание ключей. это может занять до 3 минут... + создание ключей. это может занять до 3 минут... + извлечение подписи ключа... извлечение ключа... подготовка к передаче... @@ -332,6 +365,7 @@ очень медленно Начать + ЧаВо NFC Beam Изменения О программе @@ -360,6 +394,8 @@ OpenKeychain: Расшифровать Нет связанных программ!\n\nСторонние программы могут запросить доступ к OpenKeychain, после чего они будут отражаться здесь. + Показать подробную информацию + Скрыть подробную информацию Показать расширенные настройки Скрыть расширенные настройки Ключ не выбран @@ -395,17 +431,26 @@ создать свой ключ Импортировать ключи + Изменить ключ Зашифровать для этого получателя Сертифицировать ключ этого контакта Информация Сертификация - Контакты - Зашифровать - Расшифровать + Ключи + Подписать и зашифровать + Расшифровать и проверить Импорт ключей Мои ключи Связанные приложения Открыть панель навигации Закрыть панель навигации + Изменить + Мои ключи + Секретный ключ + доступен + не доступен + Подписываемые ID пользователя + + Напишите сообщение здесь, что бы зашифровать и/или подписать... diff --git a/OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml b/OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml index 6bb115049..7b71d3ecf 100644 --- a/OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-sl-rSI/strings.xml @@ -7,6 +7,7 @@ + + diff --git a/OpenPGP-Keychain/src/main/res/values-tr/strings.xml b/OpenPGP-Keychain/src/main/res/values-tr/strings.xml index 5bb5225b5..db1d438dd 100644 --- a/OpenPGP-Keychain/src/main/res/values-tr/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-tr/strings.xml @@ -80,10 +80,10 @@ Uyarı Hata Hata: %s + Başarıyla silindi. Önce bir dosya seçin. - Başarıyla şifrelendi. Anahtar %08X bulunamadı. Anahtar başarıyla imzalandı Liste boş! @@ -95,7 +95,6 @@ anahtar uzunluğu en az 512bit olmalı bozuk veri - bitti. kaydediliyor... alıyor... veriyor... @@ -135,4 +134,5 @@ + diff --git a/OpenPGP-Keychain/src/main/res/values-uk/strings.xml b/OpenPGP-Keychain/src/main/res/values-uk/strings.xml index 7ccb661d3..da9509822 100644 --- a/OpenPGP-Keychain/src/main/res/values-uk/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-uk/strings.xml @@ -48,6 +48,7 @@ Сертифікувати Розшифрувати Розшифрувати і Перевірити + З буфера обміну Вибрати одержувачів Шифрувати файл Зберегти @@ -55,8 +56,8 @@ Вилучити Жоден Гаразд - Змінити парольну фразу - Задати парольну фразу + Змінити нову парольну фразу + Задати нову парольну фразу Пошук Завантажити на сервер ключів Далі @@ -64,6 +65,8 @@ Буфер обміну Поділитися через… Шукати ключ + Показати додаткові налаштування + Приховати додаткові налаштування Параметри Довідка @@ -71,16 +74,18 @@ Імпорт з штрих-коду Імпорт Імпорт з NFC - Експортувати усі ключі + Експортувати усі публічні ключі + Експортувати усі секретні ключі Експорт до файлу Вилучити ключ Створити ключ Створити ключ (експерт) Пошук - Імпорт з сервера ключів + Сервер ключів + Сервер ключів… Оновити з сервера ключів Завантажити на сервер ключів - Поділитися + Поділитися… Поділитися відбитком… Поділитися цілим ключем… з… @@ -94,6 +99,8 @@ Скасувати Зашифрувати… Вибрати усе + Додати ключі + Експортувати ключі Підпис Повідомлення @@ -125,8 +132,6 @@ Назва Коментар Ел. пошта - Ід підпису користувача - Підписати листа Завантажити ключ до вибраного сервера ключів після сертифікації Відбиток Вибрати @@ -139,12 +144,18 @@ <без імені> <жоден> <без ключа> + <Немає ел. пошти> можна зашифрувати можна підписати закінчився скасовано ІД користувача + + 1 контакт + %d контакти + %d контактів + %d сервер ключів %d сервери ключів @@ -154,9 +165,6 @@ Секретний ключ: Жоден - Підписати лише - Шифрувати тільки - Шифрувати і розшифрувати 15 секунд 1 хв 3 хв @@ -176,21 +184,26 @@ Попередження Помилка Помилка: %s + + Сертифікувати + Підписати + Зашифрувати + Перевірити справжність Невірна парольна фраза. Використання вмісту буфера обміну. Спершу задайте парольну фразу. Нема встановленого сумісного менеджера файлів. Парольні фрази не збігаються. - Порожні парольні фрази не дозволені. + Будь ласка, введіть парольну фразу. Симетричне шифрування. Введіть парольну фразу для \'%s\' Ви справді хочете вилучити\n%s? Успішно вилучено. Виберіть спершу файл. - Успішно розшифровано. - Успішно зашифровано. - Успішно зашифровано до буфера обміну. + Успішно розшифровано та/або перевірено. + Успішно підписано та/або перевірено. + Успішно підписано та/або зашифровано до буфера обміну. Введіть двічі парольну фразу. Виберіть принаймні один ключ шифрування. Виберіть принаймні один ключ шифрування або ключ підпису. @@ -201,6 +214,11 @@ Ви справді хочете вилучити ключ \'%s\'?\nВи не зможете це відмінити! Ви справді хочете вилучити усі вибрані ключі?\nВи не зможете це відмінити! Ви справді хочете вилучити секретний ключ \'%s\'?\nВи не зможете це відмінити! + Ви внесли зміни до в\'язки ключів, ви б хотіли. Волієте їх зберегти? + Ви вже додали порожній ідентифікатор користувача. Справді хочете продовжити? + Справді волієте вилучити ВІДКРИТИЙ ключ \'%s\'?\nВи е зможете відмінити цю дію! + Видалити секретні ключі? + Також експортувати секретні ключі? Успішно додано %d ключ Успішно додано %d ключі @@ -226,6 +244,7 @@ Успішно експортовано %d ключів. Жодного ключа не експортовано. Примітка: тільки підключі підтримують ElGamal, а для ElGamal буде використаний найближчий розмір ключа з 1536, 2048, 3072, 4096, або 8192. + Примітка: генерація ключа RSA з довжиною 1024 біти і менше вважається небезпечною і вона вимкнена для генерації нових ключів. Не можливо знайти ключ %08X. Знайдено %d ключ. @@ -259,6 +278,7 @@ основний ключ не може бути ключем ElGamal вибір невідомого алгоритму вам потрібно вказати назву + жодного листа не знайдено вам потрібно вказати електронну адресу потрібний хоча б один ІД користувача ІД основного користувача не має бути порожнім @@ -279,6 +299,7 @@ NFC недоступний на вашому пристрої! Нема що імпортувати! дата завершення дії має йти після дати створення + спершу збережіть в\'язку ключів ви не можете вилучити цей контакт, тому що він ваш власний. ви не можете вилучити наступні контакти, тому що вони - ваші власні:\n%s Запит обмеженого сервера @@ -296,13 +317,13 @@ частини завантаженого файлу є вірним об\'єктом OpenPGP, але не ключем OpenPGP частин завантаженого файлу є вірним об\'єктом OpenPGP, але не ключем OpenPGP + Вам потрібно внести зміни до в\'язки ключів перед тим, як зможете їх зберегти. - готово. - cкасувати + Готово. + Скасувати збереження… імпортується… експортується… - генерується ключ, вона може тривати до 3 хвилин… будується ключ… підготовка основного ключа… сертифікація основного ключа… @@ -314,6 +335,11 @@ експортуються ключі… експортуються ключі… + + генерується ключ, це може тривати до 3 хвилини + генеруються ключі, це може тривати до 3 хвилини + генеруються ключі, це може тривати до 3 хвилини + видобування ключа підпису… видобувається ключа… підготовка потоків… @@ -344,6 +370,7 @@ дуже повільне Початок + ЧАП NFC промінь Журнал змін Про @@ -372,15 +399,22 @@ Розшифрувати з OpenKeychain Нема зареєстрованих програм!\n\nСтороні програми можуть вимагати доступ до OpenPGP Keychain. Після надання доступу вони будуть наведені тут. + Показати додаткову інформацію + Приховати додаткову інформацію Показати додаткові налаштування Приховати додаткові налаштування Не вибрано ключа Вибрати ключ + Створити новий ключ для цього профілю Зберегти Скасувати Відкликати доступ + Видалити профіль Назва пакунку SHA-256 підписку пакунку + Облікові записи + Немає облікового запису приєднаного до цієї програми. + Ця програма вимагає створення нового профілю. Будь ласка, виберіть наявний приватний ключ або створіть інший.\nПрограми обмежені використання ключів, які ви тут оберете! Показана програма запитує доступ до OpenPGP Keychain.\nДозволити доступ?\n\nУВАГА: якщо ви не знаєте, чому цей екран появився, не дозволяйте доступ! Ви можете відкликати доступ пізніше, використовуючи екран \'Зареєстровані програми\'. Дозволити доступ Не дозволити доступ @@ -407,17 +441,28 @@ створюється ваш власний ключ імпортуюся ключі. + Редагувати цей ключ Зашифрувати у цей контакт Сертифікувати ключ цього контакту Інформація Сертифікати - Контакти - Зашифрувати - Розшифрувати + Ключі + Підписати і зашифрувати + Розшифрувати і Перевірити Імпортувати ключі Мої ключі Зареєстровані програми Відкрити панель навігації Закрити панель навігації + Редагувати + Мої ключі + Секретний ключ + доступний + недоступний + ІД користувача для реєстрації + Перезастосування сертифікатів + + Напишіть повідомлення для шифрування та/або підпису… + Уведіть зашифрований текст тут для його розшифрування та/або перевірки… diff --git a/OpenPGP-Keychain/src/main/res/values-zh-rTW/strings.xml b/OpenPGP-Keychain/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..7b71d3ecf --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenPGP-Keychain/src/main/res/values-zh/strings.xml b/OpenPGP-Keychain/src/main/res/values-zh/strings.xml index 80413d589..d569050fa 100644 --- a/OpenPGP-Keychain/src/main/res/values-zh/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values-zh/strings.xml @@ -1,6 +1,7 @@ + 聯絡人 选择公钥 选择私钥 加密 @@ -50,7 +51,6 @@ 创建密钥 创建密钥(专家) 搜索 - 分享 复制到剪贴板 签署密钥 取消 @@ -79,9 +79,6 @@ 过期了 没有 - 仅签署 - 仅加密 - 签署并加密 15秒 1分钟 3分钟 @@ -97,17 +94,14 @@ 打开... 警告 错误 + 先设置密钥 安装了不匹配的文件管理器 密钥不匹配 - 不允许空的密钥 对称加密 删除成功 先选择一个文件 - 解密成功 - 加密成功 - 成功地加密到了剪贴板 输入两次密钥 选择至少一个加密密钥 选择至少一个加密密钥或者签名密钥 @@ -135,7 +129,6 @@ 损坏的数据 错误的密语 - 完成。 保存... 导入中... 导出中... @@ -183,8 +176,7 @@ 或者 - 加密 - 解密 导入密钥 我的密钥 + diff --git a/OpenPGP-Keychain/src/main/res/values/arrays.xml b/OpenPGP-Keychain/src/main/res/values/arrays.xml index 5244de419..c84c2648d 100644 --- a/OpenPGP-Keychain/src/main/res/values/arrays.xml +++ b/OpenPGP-Keychain/src/main/res/values/arrays.xml @@ -36,7 +36,7 @@ @string/key_size_4096 - @string/menu_key_server + @string/menu_import_from_key_server @string/menu_import_from_file @string/menu_import_from_qr_code @string/import_from_clipboard diff --git a/OpenPGP-Keychain/src/main/res/values/attr.xml b/OpenPGP-Keychain/src/main/res/values/attr.xml new file mode 100644 index 000000000..86622b3e0 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/values/attr.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/values/dimens.xml b/OpenPGP-Keychain/src/main/res/values/dimens.xml new file mode 100644 index 000000000..e1a7749f0 --- /dev/null +++ b/OpenPGP-Keychain/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 240dp + 0dp + \ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/values/strings.xml b/OpenPGP-Keychain/src/main/res/values/strings.xml index 350c57002..55971003b 100644 --- a/OpenPGP-Keychain/src/main/res/values/strings.xml +++ b/OpenPGP-Keychain/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Certify Decrypt Decrypt and Verify + From Clipboard Select Recipients Encrypt File Save @@ -58,8 +59,8 @@ Delete None Okay - Change Passphrase - Set Passphrase + Change New Passphrase + Set New Passphrase Search Upload To Keyserver Next @@ -77,17 +78,18 @@ Import from QR Code Import Import from NFC - Export all keys + Export all public keys Export all secret keys Export to file Delete key Create key Create key (expert) Search - Import from keyserver + Keyserver + Keyserver… Update from keyserver Upload to key server - Share + Share… Share fingerprint… Share whole key… with… @@ -101,6 +103,8 @@ Cancel Encrypt to… Select all + Add keys + Export keys Sign @@ -117,8 +121,8 @@ Share After Encryption Encryption Algorithm Hash Algorithm - Public Key - Passphrase + with Public Key + with Passphrase Passphrase Cache Message Compression File Compression @@ -133,8 +137,6 @@ Name Comment Email - Sign User Id - Sign email Upload key to selected keyserver after certification Fingerprint Select @@ -171,9 +173,6 @@ None - Sign only - Encrypt only - Sign and Encrypt 15 secs 1 min 3 mins @@ -194,21 +193,27 @@ Error Error: %s + + Certify + Sign + Encrypt + Authenticate + Wrong passphrase. Using clipboard content. Set a passphrase first. No compatible file manager installed. The passphrases didn\'t match. - Empty passphrases are not allowed. + Please enter a passphrase. Symmetric encryption. Enter passphrase for \'%s\' Are you sure you want to delete\n%s? Successfully deleted. Select a file first. - Successfully decrypted. - Successfully encrypted. - Successfully encrypted to clipboard. + Successfully decrypted and/or verified. + Successfully signed and/or encrypted. + Successfully signed and/or encrypted to clipboard. Enter the passphrase twice. Select at least one encryption key. Select at least one encryption key or a signature key. @@ -219,6 +224,11 @@ Do you really want to delete the key \'%s\'?\nYou can\'t undo this! Do you really want to delete all selected keys?\nYou can\'t undo this! Do you really want to delete the SECRET key \'%s\'?\nYou can\'t undo this! + You have made changes to the keyring, would you like to save it? + "You have added an empty user ID, are you sure you want to continue?" + Do you really want to delete the PUBLIC key \'%s\'?\nYou can\'t undo this! + Delete Secret Keys ? + Also export secret keys? Successfully added %d key @@ -242,6 +252,7 @@ Successfully exported %d keys. No keys exported. Note: only subkeys support ElGamal, and for ElGamal the nearest keysize of 1536, 2048, 3072, 4096, or 8192 will be used. + Note: generating RSA key with length 1024-bit and less is considered unsafe and it\'s disabled for generating new keys. Couldn\'t find key %08X. @@ -301,6 +312,7 @@ NFC is not available on your device! Nothing to import! expiry date must come after creation date + please save the keyring first you can not delete this contact because it is your own. you can not delete the following contacts because they are your own:\n%s Insufficient server query @@ -316,6 +328,7 @@ part of the loaded file is a valid OpenPGP object but not a OpenPGP key parts of the loaded file are valid OpenPGP objects but not OpenPGP keys + You must make changes to the keyring before you can save it Done. @@ -393,7 +406,7 @@ Please start with QR Code with ID 1 QR Code malformed! Please try again! QR Code scanning finished! - Fingerprint contained in this QR Code is too short (< 16 characters) + Fingerprint is too short (< 16 characters) Scan QR Code with \'Barcode Scanner\' To receive keys via NFC, the device needs to be unlocked. Help @@ -407,15 +420,22 @@ No registered applications!\n\nThird-party applications can request access to OpenKeychain. After granting access, they will be listed here. + Show advanced information + Hide advanced information Show advanced settings Hide advanced settings No key selected Select key + Create new key for this account Save Cancel Revoke access + Delete account Package Name SHA-256 of Package Signature + Accounts + No accounts attached to this application. + The application requests the creation of a new account. Please select an existing private key or create a new one.\nApplications are restricted to the usage of keys you select here! The displayed application requests access to OpenKeychain.\nAllow access?\n\nWARNING: If you do not know why this screen appeared, disallow access! You can revoke access later using the \'Registered Applications\' screen. Allow access Disallow access @@ -452,9 +472,9 @@ Certifications - Contacts - Encrypt - Decrypt + Keys + Sign and Encrypt + Decrypt and Verify Import Keys My Keys Registered Apps @@ -484,4 +504,8 @@ Show by known public keys Show all certificates + + Write message here to encrypt and/or sign… + Enter ciphertext here to decrypt and/or verify… + diff --git a/OpenPGP-Keychain/src/main/res/xml/adv_preferences.xml b/OpenPGP-Keychain/src/main/res/xml/adv_preferences.xml index 2705bd22f..03f93b051 100644 --- a/OpenPGP-Keychain/src/main/res/xml/adv_preferences.xml +++ b/OpenPGP-Keychain/src/main/res/xml/adv_preferences.xml @@ -35,7 +35,7 @@ android:title="@string/label_file_compression" /> @@ -45,4 +45,4 @@ android:persistent="false" android:title="@string/label_force_v3_signature" /> - \ No newline at end of file + diff --git a/OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java b/OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java new file mode 100644 index 000000000..72f29a1e3 --- /dev/null +++ b/OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/PgpKeyOperationTest.java @@ -0,0 +1,46 @@ +package org.sufficientlysecure.keychain; + +import org.junit.Before; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; + +import org.sufficientlysecure.keychain.pgp.*; +import org.spongycastle.openpgp.*; + +@RunWith(RobolectricGradleTestRunner.class) +public class PgpKeyOperationTest { + + PGPSecretKey key; + + @Before + public void setUp() throws Exception { + + /* Input */ + int algorithm = Id.choice.algorithm.dsa; + String passphrase = "swag"; + int keysize = 2048; + boolean masterKey = true; + + /* Operation */ + PgpKeyOperation keyOperations = new PgpKeyOperation(null); + key = keyOperations.createKey(algorithm, keysize, passphrase, masterKey); + + System.err.println("initialized, test key: " + PgpKeyHelper.convertKeyIdToHex(key.getKeyID())); + } + + @After + public void tearDown() { + } + + @Test + public void createTest() { + } + + @Test + public void certifyKey() { + System.err.println("swag"); + } + +} diff --git a/OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java b/OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java new file mode 100644 index 000000000..b64ffde07 --- /dev/null +++ b/OpenPGP-Keychain/src/test/java/org/sufficientlysecure/keychain/RobolectricGradleTestRunner.java @@ -0,0 +1,23 @@ +package org.sufficientlysecure.keychain; + +import org.junit.runners.model.InitializationError; +import org.robolectric.AndroidManifest; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.res.Fs; +import org.robolectric.res.FsFile; + +import org.sufficientlysecure.keychain.KeychainApplication; + +public class RobolectricGradleTestRunner extends RobolectricTestRunner { + public RobolectricGradleTestRunner(Class testClass) throws InitializationError { + super(testClass); + } + + @Override protected AndroidManifest getAppManifest(Config config) { + String myAppPath = KeychainApplication.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + String manifestPath = myAppPath + "../../../src/main/AndroidManifest.xml"; + return createAppManifest(Fs.fileFromPath(manifestPath)); + } +} + diff --git a/README.md b/README.md index 8954bdcda..4c06ad445 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev 2. Open the Android SDK Manager (shell command: ``android``). Expand the Tools directory and select "Android SDK Build-tools (Version 19.0.3)". Expand the Extras directory and install "Android Support Repository" -Select everything for the newest SDK (API-Level 19) +Select everything for the newest SDK Platform (API-Level 19) 3. Export ANDROID_HOME pointing to your Android SDK 4. Execute ``./gradlew build`` 5. You can install the app with ``adb install -r OpenPGP-Keychain/build/apk/OpenPGP-Keychain-debug-unaligned.apk`` @@ -145,15 +145,9 @@ see http://help.transifex.net/features/client/index.html#user-client * Opening braces don't go on their own line * Field names: Non-public, non-static fields start with m. * Acronyms are words: Treat acronyms as words in names, yielding !XmlHttpRequest, getUrl(), etc. +* Fully Qualify Imports: Do *not* use wildcard-imports such as ``import foo.*;`` -See http://source.android.com/source/code-style.html - -### XML Eclipse Settings -* XML Maximum line width 999 -* XML: Split multiple attributes each on a new line (Eclipse: Properties -> XML -> XML Files -> Editor) -* XML: Indent using spaces with Indention size 4 (Eclipse: Properties -> XML -> XML Files -> Editor) - -See http://www.androidpolice.com/2009/11/04/auto-formatting-android-xml-files-with-eclipse/ +The full coding style can be found at http://source.android.com/source/code-style.html ### Automated syntax check with CheckStyle @@ -227,10 +221,6 @@ Some parts (older parts and some libraries are Apache License v2, MIT X11 Licens ### Images * icon.svg modified version of kgpg_key2_kopete.svgz - -* key.svg - http://rrze-icon-set.berlios.de/ - Creative Commons Attribution Share-Alike licence 3.0 * Menu icons http://developer.android.com/design/downloads/index.html#action-bar-icon-pack diff --git a/Resources/docs/openpgp_api_1.jpg b/Resources/docs/openpgp_api_1.jpg new file mode 100644 index 000000000..3582abb8f Binary files /dev/null and b/Resources/docs/openpgp_api_1.jpg differ diff --git a/Resources/graphics/icon.png b/Resources/graphics/icon.png index de58949eb..a7b133eb9 100644 Binary files a/Resources/graphics/icon.png and b/Resources/graphics/icon.png differ diff --git a/Resources/graphics/icon.svg b/Resources/graphics/icon.svg index 4a584dcf7..2532ed83c 100644 --- a/Resources/graphics/icon.svg +++ b/Resources/graphics/icon.svg @@ -13,7 +13,6 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:export-ydpi="90" inkscape:export-xdpi="90" - inkscape:export-filename="/home/ds1/Projekte/APG Plus/Resources/icon_google_play.png" version="1.0" inkscape:output_extension="org.inkscape.output.svg.inkscape" sodipodi:docname="icon.svg" @@ -3474,19 +3473,6 @@ style="fill:url(#linearGradient9381-884-21);fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 24.453487,40.951785 c -0.278503,0.0893 -0.539938,0.227781 -0.81312,0.325928 l 0.1981,0.739317 1.195316,-1.184717 -0.009,-0.03361 c -0.188236,0.05559 -0.385377,0.09346 -0.571291,0.153077 z m 1.814688,-0.486244 -0.539453,0.540744 0.522263,1.949109 1.428138,-1.391176 -0.351176,-1.310607 c -0.353713,0.06954 -0.70601,0.126491 -1.059772,0.21193 z m 1.682677,-0.306801 0.234117,0.873739 1.078904,-1.081489 c -0.444796,0.05158 -0.866983,0.13148 -1.313021,0.20775 z m -4.897371,1.348266 c -0.284754,0.110431 -0.566288,0.210126 -0.846725,0.334933 0.333398,0.03079 0.654403,0.111242 0.981793,0.169147 l -0.135068,-0.50408 z m 7.196364,-1.640116 -0.17462,0.154843 0.432217,1.613055 1.419136,-1.42478 -0.108054,-0.403263 c -0.521384,0.0068 -1.043367,0.01983 -1.568679,0.06014 z m -5.026967,1.671134 -0.772277,0.747203 C 24.813169,42.390423 25.17,42.509101 25.520027,42.647 l -0.297149,-1.108976 z m 4.346501,-0.984552 -1.204319,1.151112 0.441221,1.646661 1.186311,-1.218322 -0.423213,-1.579451 z m 3.649798,-0.76185 -0.597659,0.592358 0.531267,1.982714 1.161709,-1.175713 -0.36018,-1.344213 c -0.249836,-0.01881 -0.48382,-0.04396 -0.735137,-0.05515 z m 1.394059,0.0947 0.207104,0.772923 0.655864,-0.643973 c -0.283232,-0.03902 -0.577362,-0.09978 -0.862968,-0.12895 z m -2.497563,1.029401 -1.419136,1.42478 0.522262,1.949109 1.419136,-1.424781 -0.522262,-1.949108 z m -4.256459,1.320605 -1.020695,1.029874 c 0.434477,0.233862 0.850384,0.49579 1.253693,0.780634 l 0.199219,-0.197453 -0.432217,-1.613055 z m 8.403743,-2.107704 -1.253522,1.236332 0.432217,1.613055 1.419135,-1.42478 -0.369186,-1.377818 c -0.08434,-0.01531 -0.144054,-0.03236 -0.228644,-0.04679 z m 0.914582,0.187155 0.198099,0.739317 0.564051,-0.583354 c -0.249905,-0.06107 -0.509667,-0.102698 -0.76215,-0.155963 z m -6.995853,2.522859 -1.195314,1.184717 0.234117,0.873738 c 0.239867,0.215264 0.463772,0.445851 0.686583,0.680464 l 0.805881,-0.756206 -0.531267,-1.982713 z m 8.48655,-2.201928 -1.112508,1.090493 0.522262,1.949108 1.204319,-1.151112 -0.495249,-1.848292 c -0.04572,-0.01294 -0.07301,-0.02753 -0.118824,-0.0402 z m -4.173651,1.22638 -1.16171,1.175713 0.432217,1.613056 1.16171,-1.175713 -0.432217,-1.613056 z m 5.005426,-0.945001 0.288145,1.075371 0.73867,-0.738198 c -0.331899,-0.121338 -0.688782,-0.230193 -1.026815,-0.337173 z m -2.450129,1.340854 -1.42814,1.391175 0.531267,1.982713 1.419135,-1.42478 -0.522262,-1.949108 z m -4.222852,1.3116 -1.428141,1.391174 0.441222,1.646662 1.410131,-1.458387 -0.423212,-1.579449 z m 8.31193,-2.047085 -1.170714,1.142107 0.441222,1.646661 1.41013,-1.458385 -0.324163,-1.209792 c -0.118639,-0.04937 -0.236932,-0.07308 -0.356475,-0.120591 z m 1.096438,0.46259 0.09905,0.369659 0.22382,-0.240063 c -0.11157,-0.05308 -0.210394,-0.07821 -0.32287,-0.129596 z m -7.119502,2.195809 -1.161709,1.175713 0.522262,1.949108 1.16171,-1.175712 -0.522263,-1.949109 z m -4.222853,1.311601 -0.573056,0.549749 c 0.314089,0.36607 0.584652,0.770104 0.855256,1.175539 l 0.141013,-0.145838 -0.423213,-1.57945 z m 8.569356,-2.296152 -1.195314,1.184717 0.432217,1.613054 1.18631,-1.218321 -0.423213,-1.57945 z m 3.683403,-0.770856 -0.622259,0.634968 0.522262,1.949108 1.18631,-1.218322 -0.252126,-0.940949 C 43.714301,42.702794 43.417501,42.564812 43.145802,42.42645 z m -9.706465,3.429255 -1.419136,1.42478 0.522262,1.949108 1.42814,-1.391174 -0.531266,-1.982714 z m 8.569356,-2.296152 -1.419135,1.42478 0.522262,1.949108 1.428139,-1.391174 -0.531266,-1.982714 z m -4.256459,1.320605 -1.419135,1.424779 0.432217,1.613057 1.419135,-1.424782 -0.432217,-1.613054 z m 7.524058,-1.223668 -0.340231,0.34329 0.432217,1.613057 1.137109,-1.133103 C 46.107003,44.18387 45.693405,43.926727 45.276292,43.656487 z m -5.192581,1.859582 -1.204319,1.151112 0.531267,1.982714 1.195314,-1.184717 -0.522262,-1.949109 z m 4.337499,-1.018157 -1.195315,1.184719 0.441221,1.646659 1.186311,-1.218322 -0.432217,-1.613056 z m -8.593956,2.338763 -1.170714,1.142108 0.441221,1.646659 1.152705,-1.209318 -0.423212,-1.579449 z m 11.140248,-1.976516 -1.41013,1.458385 0.522262,1.949109 1.41013,-1.458386 -0.522262,-1.949108 z m -8.593956,2.338763 -1.419135,1.424779 0.522262,1.949109 1.419135,-1.42478 -0.522262,-1.949108 z m -4.222852,1.3116 -1.419136,1.424779 0.432217,1.613056 1.419135,-1.424781 -0.432216,-1.613054 z m 8.569355,-2.296152 -1.419134,1.424779 0.432217,1.613056 1.419134,-1.424781 -0.432217,-1.613054 z m 5.059454,-0.74337 0.234117,0.873738 0.331227,-0.376895 C 48.159743,45.798299 47.970076,45.634344 47.779503,45.471 z m -11.339943,3.650835 -1.161709,1.175714 0.522262,1.949108 1.170714,-1.142108 -0.531267,-1.982714 z m 8.602962,-2.305156 -1.195315,1.184718 0.522262,1.949108 1.195315,-1.184717 -0.522262,-1.949109 z M 8.7331833,56.617773 c -0.080856,0.257998 -0.185146,0.528328 -0.2567787,0.789166 L 8.8412374,57.021037 8.7331833,56.617773 z m 32.0528797,-8.480489 -1.195313,1.184718 0.432216,1.613055 1.195314,-1.184717 -0.432217,-1.613056 z m 8.005304,-1.712797 -0.597656,0.592359 0.432217,1.613054 1.195314,-1.184717 c -0.32357,-0.356241 -0.684705,-0.686305 -1.029875,-1.020696 z M 9.0303324,57.726749 8.1998504,58.525565 c -0.1251137,0.570603 -0.2112478,1.15432 -0.2915036,1.73494 l 0.2251128,0.840132 1.419135,-1.424779 -0.5222622,-1.949109 z m 25.7416726,-6.89746 -1.42814,1.391175 0.531267,1.982713 1.419135,-1.42478 -0.522262,-1.949108 z m 8.569356,-2.296152 -1.428139,1.391174 0.531266,1.982714 1.419135,-1.42478 -0.522262,-1.949108 z m 4.337498,-1.018157 -1.419134,1.42478 0.441221,1.64666 1.41013,-1.458386 -0.432217,-1.613054 z m -8.593956,2.338762 -1.42814,1.391174 0.441222,1.64666 1.41013,-1.458385 -0.423212,-1.579449 z m -29.1435218,8.853506 0.1170588,0.43687 0.05821,-0.05161 C 10.05758,58.966431 9.9963541,58.836017 9.9413812,58.707248 z m 40.0599508,-10.589959 -1.186309,1.218323 0.522261,1.949107 1.18631,-1.218321 -0.522262,-1.949109 z m -8.593957,2.338762 -1.195314,1.184718 0.522262,1.949107 1.195314,-1.184716 -0.522262,-1.949109 z m 4.337499,-1.018158 -1.18631,1.218323 0.432217,1.613056 1.18631,-1.218323 -0.432217,-1.613056 z m -8.593956,2.338763 -1.16171,1.175712 0.432217,1.613056 1.152705,-1.209318 -0.423212,-1.57945 z m 13.635401,-3.149341 0.261131,0.974553 0.248421,-0.282672 C 51.124502,49.08394 50.966383,48.85514 50.786319,48.627315 z m -40.364165,11.067671 -0.174619,0.154843 0.432217,1.613056 0.505846,-0.531741 c -0.279386,-0.394934 -0.53067,-0.804288 -0.763444,-1.236158 z m 29.275056,-7.556083 -1.419135,1.42478 0.522262,1.949108 1.428139,-1.391174 -0.531266,-1.982714 z m 8.602961,-2.305157 -1.41013,1.458385 0.513257,1.915503 1.419135,-1.424779 -0.522262,-1.949109 z M 9.7326848,60.347964 8.3135497,61.772743 8.7457669,63.385799 10.173906,61.994625 9.7326848,60.347964 z m 25.7416732,-6.897461 -1.419136,1.42478 0.432217,1.613056 1.419135,-1.424781 -0.432216,-1.613055 z m 8.569355,-2.296152 -1.419134,1.424779 0.432217,1.613057 1.428139,-1.391175 -0.441222,-1.646661 z m -10.827504,3.009276 c -0.0046,0.245127 -0.0022,0.483366 -0.02154,0.726133 l 0.165613,-0.188448 -0.144072,-0.537685 z m 18.458971,-4.369778 -0.44764,0.480125 0.432217,1.613056 0.787871,-0.823417 C 52.194092,50.635328 51.955417,50.201646 51.67518,49.793849 z m -13.902951,4.301574 -1.161709,1.175712 0.522262,1.949108 1.161709,-1.175712 -0.522262,-1.949108 z m 8.602962,-2.305156 -1.195316,1.184716 0.522262,1.949108 1.186311,-1.218322 -0.513257,-1.915502 z m 4.337498,-1.018158 -1.195314,1.184718 0.441221,1.646659 1.18631,-1.218322 -0.432217,-1.613055 z m -42.9049849,11.532375 -0.125417,0.06962 c -0.027907,0.674826 0.00787,1.360371 0.042783,2.041566 L 8.2399208,63.917539 7.8077041,62.304484 z m 25.7416719,-6.89746 -0.456644,0.44652 c -0.10047,0.621903 -0.256883,1.220038 -0.450526,1.813566 l 0.144072,0.537685 1.18631,-1.218322 -0.423212,-1.579449 z m 8.569356,-2.296152 -1.204318,1.151112 0.441221,1.646659 1.195314,-1.184717 -0.432217,-1.613054 z m -30.54482,8.328532 -0.705065,0.729192 0.522261,1.949108 1.16171,-1.175712 -0.153077,-0.57129 c -0.284683,-0.293438 -0.570662,-0.608229 -0.825829,-0.931298 z m -1.219916,1.227326 -1.4191343,1.424781 0.5222619,1.949108 1.4191354,-1.42478 -0.522263,-1.949109 z m 25.741673,-6.89746 -1.419135,1.42478 0.522262,1.949108 1.419135,-1.424779 -0.522262,-1.949109 z m 8.569356,-2.296152 -1.41013,1.458386 0.513257,1.915502 1.419135,-1.424779 -0.522262,-1.949109 z m 4.337498,-1.018157 -1.41013,1.458385 0.432217,1.613056 1.419135,-1.42478 -0.441222,-1.646661 z m -8.593956,2.338762 -1.419135,1.424779 0.432217,1.613057 1.419134,-1.424781 -0.432216,-1.613055 z m 12.378174,-3.136631 -0.928885,0.969255 0.513258,1.915502 1.25352,-1.23633 c -0.249728,-0.558508 -0.545759,-1.118305 -0.837893,-1.648427 z m -39.520973,11.526083 0.414208,1.545844 0.714071,-0.695587 c -0.390445,-0.258687 -0.769263,-0.547599 -1.128279,-0.850257 z m -4.8457568,1.40647 -0.646859,0.677579 c 0.064396,0.720367 0.1518557,1.433511 0.2894373,2.155565 l 0.8886882,-0.85043 -0.5312665,-1.982714 z m 25.7416718,-6.89746 -1.195315,1.184718 0.522262,1.949107 1.20432,-1.151112 -0.531267,-1.982713 z m 8.569356,-2.296152 -1.186309,1.218322 0.513257,1.915503 1.204319,-1.151112 -0.531267,-1.982713 z m 8.602962,-2.305157 -1.18631,1.218322 0.513258,1.915504 1.204318,-1.151112 -0.531266,-1.982714 z m -38.601093,10.523222 -1.161709,1.175713 0.432217,1.613055 1.170714,-1.142108 -0.441222,-1.64666 z m 25.741674,-6.89746 -1.16171,1.175713 0.432217,1.613054 1.170714,-1.142107 -0.441221,-1.64666 z m 8.602961,-2.305157 -1.195315,1.184718 0.432217,1.613054 1.195315,-1.184717 -0.432217,-1.613055 z m -14.824125,4.224238 c -0.01593,0.03765 -0.02389,0.08134 -0.0402,0.118826 l 0.05821,-0.05161 -0.01801,-0.06721 z m 0.207104,0.772923 -1.25352,1.236332 c -0.04927,0.07437 -0.105705,0.14876 -0.15661,0.222053 l 0.513258,1.915503 1.419135,-1.424779 -0.522263,-1.949109 z m 8.569356,-2.296152 -1.419135,1.424779 0.522263,1.949109 1.419134,-1.424779 -0.522262,-1.949109 z m 8.602962,-2.305157 -1.419135,1.424779 0.522262,1.949109 1.419135,-1.424779 -0.522262,-1.949109 z m -38.567487,10.514217 -1.428139,1.391175 0.441221,1.64666 1.419135,-1.424779 -0.432217,-1.613056 z m 25.741673,-6.89746 -1.42814,1.391175 0.441221,1.64666 1.419136,-1.42478 -0.432217,-1.613055 z m 8.569356,-2.296152 -1.428139,1.391175 0.441221,1.64666 1.419135,-1.424779 -0.432217,-1.613056 z m 8.477545,-2.235534 -1.302723,1.321552 0.441221,1.64666 1.419135,-1.424779 -0.144072,-0.537686 c -0.122462,-0.344034 -0.2754,-0.671033 -0.413561,-1.005747 z m -38.939559,10.469843 -1.054302,1.038878 0.531266,1.982713 1.419136,-1.424779 -0.342171,-1.277002 c -0.189223,-0.09925 -0.370192,-0.210796 -0.553929,-0.31981 z m 1.302896,0.695413 0.09905,0.369659 0.257425,-0.249068 c -0.116016,-0.04678 -0.242034,-0.0703 -0.356475,-0.120591 z m -2.863044,0.875205 -1.161709,1.175713 0.522262,1.949109 1.161709,-1.175714 -0.522262,-1.949108 z m 25.741673,-6.89746 -1.152705,1.209317 0.513258,1.915504 1.161709,-1.175714 -0.522262,-1.949107 z m 8.602962,-2.305157 -1.186311,1.218322 0.513258,1.915504 1.195315,-1.184718 -0.522262,-1.949108 z m 4.337498,-1.018157 -1.186309,1.218323 0.441221,1.64666 1.186309,-1.218323 -0.441221,-1.64666 z M 9.1313681,67.244465 8.2180792,68.137505 c 0.06532,0.289689 0.1475965,0.550838 0.2251128,0.840133 0.068354,0.255102 0.1303727,0.522294 0.2071039,0.772922 L 9.5635848,68.85752 9.1313681,67.244465 z M 30.571561,61.499583 c -0.356828,0.448638 -0.75809,0.855854 -1.177307,1.251928 l 0.459231,1.713872 1.195315,-1.184719 -0.477239,-1.781081 z m 4.301479,-1.152578 -1.195315,1.184718 0.432217,1.613054 1.195315,-1.184717 -0.432217,-1.613055 z m 8.569356,-2.296152 -1.195314,1.184717 0.432217,1.613055 1.195314,-1.184717 -0.432217,-1.613055 z m -26.222918,7.386591 -0.714069,0.695587 0.432217,1.613054 1.195315,-1.184717 -0.252127,-0.940949 c -0.22098,-0.06106 -0.444699,-0.109715 -0.661336,-0.182975 z m -5.541818,2.169268 -1.419134,1.42478 0.522262,1.949109 1.428139,-1.391175 -0.531267,-1.982714 z m 25.741673,-6.897461 -1.410131,1.458386 0.513258,1.915504 1.42814,-1.391175 -0.531267,-1.982715 z m 17.172317,-4.601308 -1.410129,1.458385 0.513257,1.915504 1.428139,-1.391175 -0.531267,-1.982714 z m -36.015895,9.686448 0.07204,0.268842 0.19922,-0.197453 c -0.09261,-0.0175 -0.179227,-0.05176 -0.271256,-0.07139 z m 27.421939,-7.347686 -1.419135,1.42478 0.513258,1.915503 1.428139,-1.391174 -0.522262,-1.949109 z m -30.007136,8.18446 -1.419135,1.42478 0.432217,1.613056 1.42814,-1.391175 -0.441222,-1.646661 z m 17.172317,-4.601309 -1.419135,1.424781 0.432217,1.613055 1.428139,-1.391175 -0.441221,-1.646661 z m 8.569356,-2.296152 -1.419135,1.424781 0.432217,1.613055 1.428139,-1.391175 -0.441221,-1.646661 z m 8.602961,-2.305156 -1.419135,1.42478 0.441222,1.64666 1.419134,-1.424779 -0.441221,-1.646661 z m -21.455788,5.821097 c -0.362299,0.303527 -0.742129,0.584795 -1.141935,0.846252 l -0.340232,0.343291 0.522262,1.949109 1.42814,-1.391175 -0.468235,-1.747477 z m -9.312852,2.747497 -0.738671,0.738198 0.522263,1.949109 1.428139,-1.391175 -0.333167,-1.243397 c -0.285691,-0.01577 -0.596661,-0.01699 -0.878564,-0.05273 z m 1.528482,0.05868 0.189095,0.705712 0.738671,-0.738199 c -0.313681,0.0242 -0.616551,0.03276 -0.927766,0.03249 z m 4.810386,-1.000795 c -0.140151,0.06034 -0.266567,0.162508 -0.409855,0.217874 l 0.04502,0.168027 0.364833,-0.385901 z m -2.695018,0.830182 0.396199,1.478634 1.42814,-1.391175 -0.144072,-0.537685 c -0.218378,0.07192 -0.43826,0.153449 -0.663103,0.213696 -0.346732,0.09291 -0.670138,0.175199 -1.017164,0.23653 z m -13.4577222,3.678022 -0.8886882,0.85043 c 0.2083439,0.613006 0.4397248,1.210458 0.6968803,1.794265 L 10.274942,71.51234 9.7526798,69.563232 z m 8.5693562,-2.296152 -1.195315,1.184718 0.513257,1.915502 0.06721,-0.01801 1.13711,-1.133103 -0.522262,-1.949108 z m 8.569356,-2.296152 -1.16171,1.175713 0.513257,1.915502 0.06721,-0.01801 1.103503,-1.124099 -0.522261,-1.949108 z m 8.60296,-2.305156 -1.195315,1.184717 0.513257,1.915503 0.06721,-0.01801 1.137109,-1.133103 -0.522262,-1.949108 z m 17.172317,-4.601309 -1.195314,1.184718 0.522262,1.949107 1.195314,-1.184717 -0.522262,-1.949108 z m -8.593957,2.338762 -1.204318,1.151112 0.513257,1.915503 0.06721,-0.01801 1.137109,-1.133103 -0.513258,-1.915503 z m 4.328495,-1.051762 -1.195315,1.184717 0.450226,1.680266 1.18631,-1.218323 -0.441221,-1.64666 z m -34.33563,9.236222 -1.170714,1.142108 0.441222,1.646661 1.161709,-1.175714 -0.432217,-1.613055 z m 8.602961,-2.305156 -1.204319,1.151112 0.441221,1.646661 1.195315,-1.184719 -0.432217,-1.613054 z m 8.569357,-2.296153 -1.20432,1.151113 0.441221,1.646661 1.195316,-1.184719 -0.432217,-1.613055 z m 8.569355,-2.296151 -1.170714,1.142107 0.441222,1.646661 1.161709,-1.175714 -0.432217,-1.613054 z m -23.19538,7.259706 -1.419135,1.424781 0.162081,0.604895 c 0.100985,-0.05316 0.220969,-0.05921 0.336053,-0.09005 l 1.344213,-0.360181 -0.423212,-1.57945 z m 8.602961,-2.305156 -1.419135,1.42478 0.162081,0.604896 1.680267,-0.450226 -0.423213,-1.57945 z m 8.569355,-2.296152 -1.41013,1.458386 0.153077,0.57129 1.680266,-0.450226 -0.423213,-1.57945 z m 17.172318,-4.601309 -1.41013,1.458386 0.153076,0.57129 0.806528,-0.216108 c 0.342878,-0.09187 0.651309,-0.05658 0.954779,0.06833 l -0.504253,-1.881898 z m -8.593957,2.338763 -1.419135,1.42478 0.153077,0.57129 1.680266,-0.450226 -0.414208,-1.545844 z m -29.97353,8.175455 -1.419134,1.424781 0.432217,1.613055 1.419134,-1.424781 -0.432217,-1.613055 z m 8.569356,-2.296152 -1.419134,1.424781 0.117058,0.436868 1.680266,-0.450226 -0.37819,-1.411423 z m 8.569356,-2.296152 -1.419135,1.424781 0.117059,0.436868 1.680266,-0.450226 -0.37819,-1.411423 z m 8.602961,-2.305157 -1.419135,1.424782 0.117058,0.436868 1.680267,-0.450226 -0.37819,-1.411424 z m 8.569356,-2.296152 -1.419134,1.424782 0.117058,0.436867 1.680266,-0.450225 -0.37819,-1.411424 z m 8.544755,-2.253542 -1.360928,1.373167 0.441221,1.646659 1.152705,-1.209317 c -0.04622,-0.610934 -0.134328,-1.197792 -0.232998,-1.810509 z m -40.566917,12.058469 -1.161709,1.175713 0.522262,1.949109 0.68947,-0.652978 -0.153077,-0.57129 c -0.126204,-0.471001 7.12e-4,-0.930958 0.265136,-1.295657 l -0.162082,-0.604897 z m 8.602962,-2.305156 -0.398439,0.394905 0.470475,-0.126063 -0.07204,-0.268842 z m 8.569356,-2.296152 -0.364833,0.385901 0.436869,-0.117059 -0.07204,-0.268842 z m 17.172317,-4.601309 -0.364833,0.385901 0.436869,-0.117058 -0.07204,-0.268843 z m -8.593957,2.338762 -0.340233,0.343292 0.403264,-0.108055 -0.06303,-0.235237 z m -29.97353,8.175456 -0.622258,0.634969 c 0.217477,0.456 0.434455,0.894051 0.680637,1.330382 l 0.382842,-0.318691 -0.441221,-1.64666 z m 8.569356,-2.296152 -0.141013,0.145838 0.168026,-0.04502 -0.02701,-0.100815 z m 8.569356,-2.296152 -0.141013,0.145838 0.168026,-0.04502 -0.02701,-0.100815 z m 8.60296,-2.305157 -0.141013,0.145839 0.168027,-0.04502 -0.02701,-0.100816 z m 8.569356,-2.296152 -0.141012,0.145838 0.168026,-0.04502 -0.02701,-0.100816 z m 8.602961,-2.305156 -1.195313,1.184717 0.207103,0.772922 0.234118,0.873738 1.195314,-1.184717 -0.441222,-1.64666 z m -40.358692,11.89462 -1.428139,1.391175 0.531265,1.982709 1.419136,-1.424775 -0.522262,-1.949109 z m 42.515552,-11.103862 -1.020696,1.029875 0.52226,1.949102 0.456646,-0.446513 c 0.06462,-0.834666 0.07352,-1.686628 0.04179,-2.532464 z M 11.076344,74.503212 10.843519,74.70967 c 0.139763,0.231452 0.319261,0.441865 0.466942,0.66728 l -0.234117,-0.873738 z m 42.913989,-11.498768 -1.186309,1.218321 0.522261,1.949106 1.186309,-1.218322 -0.522261,-1.949105 z m -39.082336,11.012351 -0.680465,0.686583 0.432217,1.613057 0.680466,-0.686582 -0.432218,-1.613058 z m -1.195315,1.184719 -1.419136,1.424777 0.06303,0.235242 c 0.255635,0.328728 0.485738,0.636179 0.758618,0.949302 l 1.038707,-0.962659 -0.441222,-1.646662 z m 41.644872,-10.186223 -0.141013,0.145836 0.09005,0.336055 c 0.0241,-0.16474 0.03055,-0.31645 0.05097,-0.481891 z m -0.655864,0.643973 -1.195314,1.184718 0.441222,1.646662 0.987089,-1.020873 c 0.03721,-0.155386 0.09327,-0.310064 0.127183,-0.466296 l -0.36018,-1.344211 z m -39.172382,10.676294 -0.680466,0.686582 0.522263,1.949113 1.103504,-1.1241 C 16.102003,77.653875 15.80009,77.34613 15.682386,76.906856 l -0.153078,-0.571297 z m -1.195315,1.184719 -0.763272,0.780804 c 0.39765,0.431059 0.798073,0.82161 1.227328,1.219922 l 0.05821,-0.05161 -0.522263,-1.949113 z m 38.864634,-9.405244 c -0.200355,0.259912 -0.449818,0.444695 -0.792696,0.536568 l -0.806528,0.216109 0.414209,1.545848 1.410128,-1.458391 -0.225113,-0.840134 z m -35.943858,9.955294 0.405204,1.512244 1.419134,-1.424788 -0.144072,-0.537682 -1.344213,0.360181 c -0.115084,0.03084 -0.222019,0.08559 -0.336053,0.09005 z m 2.285162,-0.612307 0.05403,0.201637 0.282025,-0.291683 -0.336053,0.09005 z m 2.016319,-0.540272 0.450226,1.680267 1.419135,-1.424777 -0.189096,-0.705715 -1.680265,0.450225 z m 2.285161,-0.612307 0.09905,0.36966 0.539451,-0.540746 -0.638501,0.171086 z m 2.016319,-0.540271 0.405205,1.512244 1.419133,-1.424787 -0.144071,-0.537683 -1.680267,0.450226 z m 2.285162,-0.612307 0.05403,0.201637 0.282025,-0.291682 -0.336054,0.09004 z m 1.982714,-0.531267 0.450226,1.680267 1.419135,-1.424777 -0.189096,-0.705715 -1.680265,0.450225 z m 2.285161,-0.612307 0.09905,0.36966 0.539452,-0.540746 -0.638502,0.171086 z m 2.016319,-0.540271 0.414209,1.545849 1.419132,-1.424787 -0.153075,-0.571287 -1.680266,0.450225 z m 2.285161,-0.612307 0.06303,0.235242 0.340232,-0.343296 -0.403265,0.108054 z m 2.016319,-0.540271 0.450226,1.680267 1.419137,-1.424778 -0.189096,-0.705715 -1.680267,0.450226 z m 2.285162,-0.612307 0.09905,0.36966 0.53945,-0.540746 -0.6385,0.171086 z m 1.982714,-0.531267 0.414209,1.545849 1.419132,-1.424787 -0.153076,-0.571287 -1.680265,0.450225 z m 2.285162,-0.612307 0.06303,0.235242 0.340229,-0.343296 -0.403262,0.108054 z m 2.016319,-0.540271 0.45923,1.713871 1.419136,-1.424777 -0.1981,-0.73932 -1.680266,0.450226 z m 2.285161,-0.612307 0.09905,0.36966 0.539451,-0.540746 -0.638501,0.171086 z m -28.604717,7.772662 -1.195314,1.184719 0.513258,1.915508 1.204319,-1.151114 -0.522263,-1.949113 z m 8.569356,-2.296152 -1.161709,1.175714 0.513259,1.915509 1.170713,-1.14211 -0.522263,-1.949113 z m 8.60296,-2.305156 -1.195314,1.184719 0.513259,1.915508 1.204319,-1.151114 -0.522264,-1.949113 z m 8.569356,-2.296152 -1.186309,1.218323 0.513259,1.915508 1.195314,-1.184718 -0.522264,-1.949113 z m -30.007133,8.184467 -1.161709,1.175714 0.189093,0.705705 c 0.214273,0.178473 0.387276,0.35082 0.607956,0.52145 l 0.805882,-0.756207 -0.441222,-1.646662 z m 8.602961,-2.305157 -1.195314,1.184719 0.432214,1.613048 1.204322,-1.151105 -0.441222,-1.646662 z m 8.569357,-2.296152 -1.195316,1.184719 0.44122,1.646652 1.204322,-1.151104 -0.450226,-1.680267 z m 8.569355,-2.296152 -1.161709,1.175714 0.441219,1.646653 1.170716,-1.1421 -0.450226,-1.680267 z m 8.602961,-2.305156 -1.18631,1.218323 0.432215,1.613048 1.195317,-1.184709 -0.441222,-1.646662 z m 3.517786,-0.582416 -0.447639,0.480128 0.144074,0.537692 c 0.116139,-0.330894 0.202775,-0.681866 0.303565,-1.01782 z m -0.96249,0.978265 -1.419135,1.424777 0.522263,1.949113 0.141014,-0.145837 c 0.382886,-0.719825 0.730277,-1.432221 1.034997,-2.186293 l -0.279139,-1.04176 z m -34.335629,9.236221 -1.428141,1.391173 0.522264,1.949113 1.419136,-1.42478 -0.513259,-1.915506 z m 8.602961,-2.305157 -1.42814,1.391174 0.522263,1.949112 1.419136,-1.424777 -0.513259,-1.915509 z m 8.569355,-2.296151 -1.428139,1.391173 0.522263,1.949112 1.419135,-1.424777 -0.513259,-1.915508 z m 8.569356,-2.296152 -1.419135,1.424777 0.513259,1.915508 1.428139,-1.391172 -0.522263,-1.949113 z m -21.404175,5.8793 -1.428137,1.391183 0.44122,1.646653 1.419135,-1.424778 -0.432218,-1.613058 z m 8.569356,-2.296152 -1.428137,1.391183 0.450226,1.680266 1.419133,-1.424787 -0.441222,-1.646662 z m 8.602962,-2.305156 -1.428138,1.391182 0.450226,1.680267 1.419133,-1.424787 -0.441221,-1.646662 z m 8.578362,-2.262538 -1.428139,1.391173 0.441222,1.646662 1.419132,-1.424787 -0.432215,-1.613048 z m 2.313467,0.568699 -1.18631,1.218323 0.522263,1.949112 1.18631,-1.218323 -0.522263,-1.949112 z m -34.33563,9.236223 -0.456643,0.44652 c 0.235943,0.17204 0.466314,0.33126 0.708769,0.49443 l -0.252126,-0.94095 z m 8.602961,-2.305158 -1.195314,1.184718 0.513258,1.91551 1.195315,-1.18472 -0.513259,-1.915508 z m 8.569357,-2.296153 -1.195316,1.184719 0.513259,1.915508 1.195316,-1.184718 -0.513259,-1.915509 z m 8.569355,-2.296151 -1.152705,1.209318 0.513259,1.915508 1.161709,-1.175714 -0.522263,-1.949112 z m -21.404175,5.8793 -1.195314,1.184722 0.432217,1.61305 1.195315,-1.18471 -0.432218,-1.613062 z m 8.569356,-2.296152 -1.161709,1.175714 0.441222,1.646662 1.161709,-1.175714 -0.441222,-1.646662 z m 8.602961,-2.305156 -1.195315,1.184718 0.441222,1.646662 1.195314,-1.184718 -0.441221,-1.646662 z m 8.578362,-2.262538 -1.195316,1.184709 0.432217,1.613057 1.195314,-1.184718 -0.432215,-1.613048 z m -23.19538,7.259705 -1.419136,1.424773 0.513259,1.91551 1.428137,-1.39118 -0.52226,-1.949103 z m 8.569356,-2.296152 -1.419136,1.424777 0.513259,1.915508 1.428137,-1.391182 -0.52226,-1.949103 z m 8.602961,-2.305157 -1.410132,1.458382 0.513259,1.915509 1.419134,-1.424788 -0.522261,-1.949103 z m 8.569356,-2.296152 -1.419136,1.424778 0.522264,1.949112 1.419132,-1.424787 -0.52226,-1.949103 z M 19.970555,81.48471 18.934262,82.5908 c 0.429888,0.24941 0.856871,0.47298 1.302895,0.69542 l 0.17462,-0.15484 -0.441222,-1.64667 z m 8.602961,-2.305153 -1.419136,1.424777 0.441222,1.646666 1.42814,-1.39118 -0.450226,-1.680263 z m 8.569355,-2.296152 -1.419135,1.424777 0.441221,1.646662 1.42814,-1.391173 -0.450226,-1.680266 z m 8.57836,-2.262548 -1.419135,1.424778 0.432217,1.613057 1.42814,-1.391173 -0.441222,-1.646662 z m -23.410194,7.533383 -1.204319,1.15111 0.117059,0.43687 c 0.444778,0.19669 0.91894,0.39769 1.376697,0.56758 l 0.223819,-0.24007 -0.513256,-1.91549 z m 8.569356,-2.296157 -1.170714,1.142107 0.522261,1.94911 1.161709,-1.17572 -0.513256,-1.915497 z m 8.60296,-2.305156 -1.195315,1.184718 0.513257,1.915499 1.204321,-1.151104 -0.522263,-1.949113 z m 8.569356,-2.296152 -1.204318,1.151114 0.522261,1.949103 1.20432,-1.151104 -0.522263,-1.949113 z m -21.404175,5.879305 -1.204319,1.15111 0.450226,1.68027 1.195315,-1.18472 -0.441222,-1.64666 z m 8.569357,-2.296157 -1.20432,1.151114 0.450226,1.680263 1.195316,-1.184715 -0.441222,-1.646662 z m 8.578359,-2.262547 -1.170713,1.142109 0.441222,1.646662 1.161709,-1.175714 -0.432218,-1.613057 z m 7.74788,-1.463724 -0.349237,0.309681 0.02701,0.100814 c 0.09969,-0.140033 0.225699,-0.268515 0.322224,-0.410495 z m -22.340296,6.418278 -1.419137,1.42478 0.513257,1.9155 1.419136,-1.42478 -0.513256,-1.9155 z m 8.569355,-2.296148 -1.419136,1.424777 0.522261,1.949101 1.419135,-1.42478 -0.52226,-1.949098 z m 8.569355,-2.296152 -1.419135,1.424777 0.522261,1.949103 1.419135,-1.424777 -0.522261,-1.949103 z m -21.404174,5.8793 -1.419136,1.42478 0.117059,0.43687 c 0.394597,0.12628 0.776933,0.26212 1.179247,0.36837 l 0.564052,-0.58336 -0.441222,-1.64666 z m 8.569356,-2.296152 -1.419136,1.424782 0.441222,1.64666 1.419135,-1.42478 -0.441221,-1.646662 z m 8.611965,-2.271552 -1.419136,1.424778 0.432217,1.613056 1.419137,-1.424776 -0.432218,-1.613058 z m 8.569356,-2.296152 -1.419135,1.424778 0.117059,0.436869 c 0.477746,-0.522483 0.928752,-1.10114 1.356102,-1.660019 l -0.05403,-0.201628 z m -23.419199,7.499776 -1.195315,1.18472 0.189093,0.7057 c 0.438297,0.08759 0.884162,0.19249 1.329264,0.25614 l 0.199218,-0.19746 -0.52226,-1.9491 z m 8.569356,-2.29615 -1.186311,1.21832 0.513256,1.9155 1.195316,-1.18472 -0.522261,-1.9491 z m 8.569355,-2.296156 -1.161709,1.175714 0.522261,1.949102 1.161709,-1.17571 -0.522261,-1.949106 z m -12.834819,3.583146 -1.161709,1.17572 0.441222,1.64666 1.170714,-1.14211 -0.450227,-1.68027 z m 8.611965,-2.27155 -1.195314,1.18472 0.432217,1.61306 1.204319,-1.15112 -0.441222,-1.64666 z m 8.569356,-2.296152 -1.195314,1.184719 0.108055,0.403264 c 0.406645,-0.365398 0.834514,-0.728004 1.213322,-1.117509 l -0.126063,-0.470474 z m -23.186376,7.293312 -0.05821,0.05161 c 0.02091,0.0049 0.04628,-0.02281 0.06721,-0.01801 l -0.009,-0.0336 z m 17.172317,-4.601311 -1.428138,1.391181 0.396198,1.47863 c 0.421481,-0.19782 0.832645,-0.40094 1.23857,-0.62002 l 0.315633,-0.30068 -0.522263,-1.949111 z m -8.593957,2.338761 -1.428137,1.39118 0.387193,1.44503 c 0.232467,-0.01947 0.466108,-0.05312 0.69912,-0.07928 l 0.855083,-0.84142 -0.513259,-1.91551 z m -4.240859,1.2444 -1.428141,1.39117 0.04502,0.16802 c 0.598027,0.06253 1.183809,0.07812 1.790735,0.09647 l 0.03361,-0.009 -0.441222,-1.64666 z m 8.578359,-2.26255 -1.42814,1.39117 0.441222,1.64666 1.419135,-1.42477 -0.432217,-1.61306 z m 8.569356,-2.296152 -1.42814,1.391172 0.03602,0.13442 c 0.512616,-0.370138 1.012852,-0.750913 1.491173,-1.155932 l -0.09905,-0.36966 z m -6.246885,2.898452 -1.195315,1.18472 0.153079,0.5713 c 0.462149,-0.17092 0.920198,-0.35074 1.366399,-0.54622 l -0.324163,-1.2098 z m -8.593954,2.33877 -1.029702,0.99627 c 0.424597,-0.0025 0.854389,-0.03061 1.281828,-0.05532 l -0.252126,-0.94095 z m 4.337499,-1.01816 -1.195316,1.18472 0.117059,0.43687 c 0.476116,-0.08601 0.943997,-0.22979 1.420428,-0.34458 l -0.342171,-1.27701 z m 2.54629,0.36225 -0.456643,0.44652 c 0.172308,-0.05482 0.367383,-0.08585 0.537685,-0.14407 l -0.08104,-0.30245 z" id="path3410" /> - OXYGEN + +Designers: +David J. Miller +David Vignoni +Johann Ollivier Lapeyre +Kenneth Wimer +Nuno F. Pinheiro +Riccardo Iaconelli + +Thanks to: +Lee Olson: Contributed drawing used in application-x-bittorent icon. +Marco Aurélio "Coré": Improved audio-input-microphone icon. +Matthias Kretz: Contributed "audio-input-line" device icon. +Mauricio Piacentini : game icons mashup + +Repackaged by Luke Channings \ No newline at end of file diff --git a/Resources/graphics/icon/COPYING b/Resources/graphics/icon/COPYING new file mode 100644 index 000000000..2faa27568 --- /dev/null +++ b/Resources/graphics/icon/COPYING @@ -0,0 +1,48 @@ +The Oxygen Icon Theme for KDE3 + Copyright (C) 2007 David Vignoni + Copyright (C) 2007 Johann Ollivier Lapeyre + Copyright (C) 2007 Kenneth Wimer + Copyright (C) 2007 Nuno Fernades Pinheiro + Copyright (C) 2007 Riccardo Iaconelli + Copyright (C) 2007 David Miller + +and others + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library. If not, see . + +Clarification: + + The GNU Lesser General Public License or LGPL is written for + software libraries in the first place. We expressly want the LGPL to + be valid for this artwork library too. + + KDE Oxygen theme icons is a special kind of software library, it is an + artwork library, it's elements can be used in a Graphical User Interface, or + GUI. + + Source code, for this library means: + - where they exist, SVG; + - otherwise, if applicable, the multi-layered formats xcf or psd, or + otherwise png. + + The LGPL in some sections obliges you to make the files carry + notices. With images this is in some cases impossible or hardly useful. + + With this library a notice is placed at a prominent place in the directory + containing the elements. You may follow this practice. + + The exception in section 5 of the GNU Lesser General Public License covers + the use of elements of this art library in a GUI. + + kde-artists [at] kde.org diff --git a/Resources/graphics/kgpg_key2_kopete.svgz b/Resources/graphics/icon/kgpg_key2_kopete.svgz similarity index 100% rename from Resources/graphics/kgpg_key2_kopete.svgz rename to Resources/graphics/icon/kgpg_key2_kopete.svgz diff --git a/Resources/graphics/revokedKey.png b/Resources/graphics/revokedKey.png new file mode 100644 index 000000000..eec2bca99 Binary files /dev/null and b/Resources/graphics/revokedKey.png differ diff --git a/Resources/graphics/revokedKey.svg b/Resources/graphics/revokedKey.svg new file mode 100644 index 000000000..c7f3f2f76 --- /dev/null +++ b/Resources/graphics/revokedKey.svg @@ -0,0 +1,14909 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + email + send + mail + user + not available + + + + Sept 2009 + + + Franziska Sponsel + + + + + Franziska Sponsel + + + + + RRZE + + + + + Beate Kaspar, Hendrik Eggers + + + uses < http://ftp.uni-erlangen.de/pub/rrze/tango/rrze-icon-set/tango/scalable/categories/email.svg>, < http://ftp.uni-erlangen.de/pub/rrze/tango/rrze-icon-set/tango/scalable/actions/action-undo.svg> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/graphics/update-icon.sh b/Resources/graphics/update-icon.sh index 307541f2c..3df587a21 100755 --- a/Resources/graphics/update-icon.sh +++ b/Resources/graphics/update-icon.sh @@ -1,6 +1,6 @@ #!/bin/bash -APP_DIR=../../OpenPGP-Keychain +APP_DIR=../../OpenPGP-Keychain/src/main LDPI_DIR=$APP_DIR/res/drawable-ldpi MDPI_DIR=$APP_DIR/res/drawable-mdpi HDPI_DIR=$APP_DIR/res/drawable-hdpi diff --git a/tools/checkstyle.xml b/tools/checkstyle.xml index 26a3f8c02..c99f858ab 100644 --- a/tools/checkstyle.xml +++ b/tools/checkstyle.xml @@ -9,10 +9,9 @@ page at http://checkstyle.sourceforge.net/config.html --> - - + + + + + + + + diff --git a/tools/suppressions.xml b/tools/suppressions.xml new file mode 100644 index 000000000..9173d3a5e --- /dev/null +++ b/tools/suppressions.xml @@ -0,0 +1,12 @@ + + + + + + + + + +