diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-08-28 11:00:18 +0200 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2014-08-28 11:00:18 +0200 |
commit | c0ebc926117d3e444c7c32bf3251880852000df6 (patch) | |
tree | b84926b3eee5cf1cfd6d38a2c3bec23e8f0b6779 /OpenKeychain/src/main/java/org/sufficientlysecure/keychain | |
parent | ad69e47cec58287d82978b28416db50f6c3feb77 (diff) | |
parent | b193965e585d0d492cf2744e3c86ecc9e45b71d4 (diff) | |
download | open-keychain-c0ebc926117d3e444c7c32bf3251880852000df6.tar.gz open-keychain-c0ebc926117d3e444c7c32bf3251880852000df6.tar.bz2 open-keychain-c0ebc926117d3e444c7c32bf3251880852000df6.zip |
Merge branch 'master' into yubikey
Conflicts:
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain')
77 files changed, 3041 insertions, 1309 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 755d74ac2..9f84da815 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -35,6 +35,9 @@ public final class Constants { public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain"; + public static final String ACCOUNT_NAME = "OpenKeychain"; + public static final String ACCOUNT_TYPE = PACKAGE_NAME + ".account"; + // as defined in http://tools.ietf.org/html/rfc3156, section 7 public static final String NFC_MIME = "application/pgp-keys"; @@ -68,11 +71,14 @@ public final class Constants { public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion"; public static final String WRITE_VERSION_HEADER = "writeVersionHeader"; public static final String FIRST_TIME = "firstTime"; + public static final String CACHED_CONSOLIDATE = "cachedConsolidate"; + public static final String CACHED_CONSOLIDATE_SECRETS = "cachedConsolidateSecrets"; + public static final String CACHED_CONSOLIDATE_PUBLICS = "cachedConsolidatePublics"; } public static final class Defaults { - public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, subkeys.pgp.net, hkps://pgp.mit.edu"; - public static final int KEY_SERVERS_VERSION = 2; + public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu"; + public static final int KEY_SERVERS_VERSION = 3; } public static final class DrawerItems { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index 9b9880533..57d74967b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -20,15 +20,20 @@ package org.sufficientlysecure.keychain; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Environment; +import android.provider.ContactsContract; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.TlsHelper; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PRNGFixes; @@ -89,14 +94,37 @@ public class KeychainApplication extends Application { TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer"); TemporaryStorageProvider.cleanUp(this); + + checkConsolidateRecovery(); + + } + + public void checkConsolidateRecovery() { + + // restart consolidate process if it has been interruped before + if (Preferences.getPreferences(this).getCachedConsolidate()) { + // do something which calls ProviderHelper.consolidateDatabaseStep2 with a progressable + Intent consolidateIntent = new Intent(this, ConsolidateDialogActivity.class); + consolidateIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(consolidateIntent); + } + } public static void setupAccountAsNeeded(Context context) { - AccountManager manager = AccountManager.get(context); - Account[] accounts = manager.getAccountsByType(Constants.PACKAGE_NAME); - if (accounts == null || accounts.length == 0) { - Account dummy = new Account(context.getString(R.string.app_name), Constants.PACKAGE_NAME); - manager.addAccountExplicitly(dummy, null, null); + // only enabled for Jelly Bean because we need some newer methods in our sync adapter + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + AccountManager manager = AccountManager.get(context); + Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE); + if (accounts == null || accounts.length == 0) { + Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); + if (manager.addAccountExplicitly(account, null, null)) { + ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); + ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); + } else { + Log.e(Constants.TAG, "Adding account failed!"); + } + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java index 8697e49f7..016d6dc14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.helper; import android.accounts.Account; import android.accounts.AccountManager; +import android.annotation.TargetApi; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; @@ -234,6 +235,25 @@ public class ContactHelper { return new ArrayList<String>(mails); } + public static List<String> getContactNames(Context context) { + ContentResolver resolver = context.getContentResolver(); + Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, + new String[]{ContactsContract.Contacts.DISPLAY_NAME}, + null, null, null); + if (cursor == null) return null; + + Set<String> names = new HashSet<String>(); + while (cursor.moveToNext()) { + String name = cursor.getString(0); + if (name != null) { + names.add(name); + } + } + cursor.close(); + return new ArrayList<String>(names); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public static Uri dataUriFromContactUri(Context context, Uri contactUri) { Cursor contactMasterKey = context.getContentResolver().query(contactUri, new String[]{ContactsContract.Data.DATA2}, null, null, null, null); @@ -323,7 +343,7 @@ public class ContactHelper { // Delete fingerprints that are no longer present in OK for (String fingerprint : contactFingerprints) { resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, - new String[]{Constants.PACKAGE_NAME, fingerprint}); + new String[]{Constants.ACCOUNT_TYPE, fingerprint}); } } @@ -334,7 +354,7 @@ public class ContactHelper { private static Set<String> getRawContactFingerprints(ContentResolver resolver) { HashSet<String> result = new HashSet<String>(); Cursor fingerprints = resolver.query(ContactsContract.RawContacts.CONTENT_URI, SOURCE_ID_PROJECTION, - ACCOUNT_TYPE_SELECTION, new String[]{Constants.PACKAGE_NAME}, null); + ACCOUNT_TYPE_SELECTION, new String[]{Constants.ACCOUNT_TYPE}, null); if (fingerprints != null) { while (fingerprints.moveToNext()) { result.add(fingerprints.getString(0)); @@ -349,10 +369,11 @@ public class ContactHelper { * * @return raw contact id or -1 if not found */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private static int findRawContactId(ContentResolver resolver, String fingerprint) { int rawContactId = -1; Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, ID_PROJECTION, - ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, new String[]{Constants.PACKAGE_NAME, fingerprint}, null, null); + ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, new String[]{Constants.ACCOUNT_TYPE, fingerprint}, null, null); if (raw != null) { if (raw.moveToNext()) { rawContactId = raw.getInt(0); @@ -367,8 +388,8 @@ public class ContactHelper { */ private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, String fingerprint) { ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) - .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, context.getString(R.string.app_name)) - .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.PACKAGE_NAME) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE) .withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint) .build()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java index 72e88d793..5190a550e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -25,7 +25,12 @@ import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Constants.Pref; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.ListIterator; import java.util.Vector; /** @@ -130,6 +135,36 @@ public class Preferences { editor.commit(); } + public boolean getCachedConsolidate() { + return mSharedPreferences.getBoolean(Pref.CACHED_CONSOLIDATE, false); + } + + public void setCachedConsolidate(boolean value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Pref.CACHED_CONSOLIDATE, value); + editor.commit(); + } + + public int getCachedConsolidateNumPublics() { + return mSharedPreferences.getInt(Pref.CACHED_CONSOLIDATE_PUBLICS, -1); + } + + public void setCachedConsolidateNumPublics(int value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(Pref.CACHED_CONSOLIDATE_PUBLICS, value); + editor.commit(); + } + + public int getCachedConsolidateNumSecrets() { + return mSharedPreferences.getInt(Pref.CACHED_CONSOLIDATE_SECRETS, -1); + } + + public void setCachedConsolidateNumSecrets(int value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(Pref.CACHED_CONSOLIDATE_SECRETS, value); + editor.commit(); + } + public boolean isFirstTime() { return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true); } @@ -175,15 +210,24 @@ public class Preferences { // migrate keyserver to hkps if (mSharedPreferences.getInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, 0) != Constants.Defaults.KEY_SERVERS_VERSION) { - String[] servers = getKeyServers(); - for (int i = 0; i < servers.length; i++) { - if (servers[i].equals("pool.sks-keyservers.net")) { - servers[i] = "hkps://hkps.pool.sks-keyservers.net"; - } else if (servers[i].equals("pgp.mit.edu")) { - servers[i] = "hkps://pgp.mit.edu"; + String[] serversArray = getKeyServers(); + ArrayList<String> servers = new ArrayList<String>(Arrays.asList(serversArray)); + ListIterator<String> it = servers.listIterator(); + while (it.hasNext()) { + String server = it.next(); + if (server.equals("pool.sks-keyservers.net")) { + // use HKPS! + it.set("hkps://hkps.pool.sks-keyservers.net"); + } else if (server.equals("pgp.mit.edu")) { + // use HKPS! + it.set("hkps://pgp.mit.edu"); + } else if (server.equals("subkeys.pgp.net")) { + // remove, because often down and no HKPS! + it.remove(); } + } - setKeyServers(servers); + setKeyServers(servers.toArray(new String[servers.size()])); mSharedPreferences.edit() .putInt(Constants.Pref.KEY_SERVERS_DEFAULT_VERSION, Constants.Defaults.KEY_SERVERS_VERSION) .commit(); @@ -193,6 +237,11 @@ public class Preferences { if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0) == 0x21070001) { setDefaultFileCompression(CompressionAlgorithmTags.UNCOMPRESSED); } + + // migrate away from MD5 + if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, 0) == HashAlgorithmTags.MD5) { + setDefaultHashAlgorithm(HashAlgorithmTags.SHA512); + } } public void setWriteVersionHeader(boolean conceal) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index f617be62a..e30401180 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -24,8 +24,10 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.Log; -import java.io.DataOutputStream; +import java.io.BufferedWriter; import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; @@ -281,7 +283,7 @@ public class HkpKeyserver extends Keyserver { entry.setBitStrength(Integer.parseInt(matcher.group(3))); final int algorithmId = Integer.decode(matcher.group(2)); - entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); + entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId, null, null)); // group 1 contains the full fingerprint (v4) or the long key id if available // see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr @@ -352,25 +354,38 @@ public class HkpKeyserver extends Keyserver { @Override public void add(String armoredKey) throws AddKeyException { try { - String query = getUrlPrefix() + mHost + ":" + mPort + "/pks/add"; + String request = "/pks/add"; String params; try { - params = "keytext=" + URLEncoder.encode(armoredKey, "utf8"); + params = "keytext=" + URLEncoder.encode(armoredKey, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AddKeyException(); } - Log.d(Constants.TAG, "hkp keyserver add: " + query); - - HttpURLConnection connection = openConnection(new URL(query)); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - connection.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length)); - connection.setDoOutput(true); - DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); - wr.writeBytes(params); - wr.flush(); - wr.close(); + URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); + + Log.d(Constants.TAG, "hkp keyserver add: " + url.toString()); + Log.d(Constants.TAG, "params: " + params); + + HttpURLConnection conn = openConnection(url); + conn.setRequestMethod("POST"); + conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + conn.setRequestProperty("Content-Length", Integer.toString(params.getBytes().length)); + conn.setDoInput(true); + conn.setDoOutput(true); + + OutputStream os = conn.getOutputStream(); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); + writer.write(params); + writer.flush(); + writer.close(); + os.close(); + + conn.connect(); + + Log.d(Constants.TAG, "response code: " + conn.getResponseCode()); + Log.d(Constants.TAG, "answer: " + readAll(conn.getInputStream(), conn.getContentEncoding())); } catch (IOException e) { + Log.e(Constants.TAG, "IOException", e); throw new AddKeyException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 30e93f957..da70f1505 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -39,7 +39,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable { private boolean mExpired; private Date mDate; // TODO: not displayed private String mFingerprintHex; - private int mBitStrength; + private Integer mBitStrength; + private String mCurveOid; private String mAlgorithm; private boolean mSecretKey; private String mPrimaryUserId; @@ -162,10 +163,14 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.mFingerprintHex = fingerprintHex; } - public int getBitStrength() { + public Integer getBitStrength() { return mBitStrength; } + public String getCurveOid() { + return mCurveOid; + } + public void setBitStrength(int bitStrength) { this.mBitStrength = bitStrength; } @@ -258,13 +263,15 @@ public class ImportKeysListEntry implements Serializable, Parcelable { mPrimaryUserId = mUserIds.get(0); } - this.mKeyId = key.getKeyId(); - this.mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId); + mKeyId = key.getKeyId(); + mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId); - this.mRevoked = key.isRevoked(); - this.mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); - this.mBitStrength = key.getBitStrength(); + mRevoked = key.isRevoked(); + mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); + mBitStrength = key.getBitStrength(); + mCurveOid = key.getCurveOid(); final int algorithm = key.getAlgorithm(); - this.mAlgorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm); + mAlgorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm, mBitStrength, mCurveOid); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index 0ca6f07fd..cbd06da90 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -75,7 +75,7 @@ public class KeybaseKeyserver extends Keyserver { entry.setExtraData(username); final int algorithmId = match.getAlgorithmId(); - entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId)); + entry.setAlgorithm(PgpKeyHelper.getAlgorithmInfo(algorithmId, null, null)); final int bitStrength = match.getBitStrength(); entry.setBitStrength(bitStrength); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java index 8609a7082..55e6be9b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index 1da66872d..8076a14f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index ce6498df1..7a63a7a42 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 972e45c2e..2c27c5c37 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -60,10 +61,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { return mRing; } - public void encode(ArmoredOutputStream stream) throws IOException { - getRing().encode(stream); - } - /** Getter that returns the subkey that should be used for signing. */ CanonicalizedPublicKey getEncryptionSubKey() throws PgpGeneralException { PGPPublicKey key = getRing().getPublicKey(getEncryptId()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 877857553..c79dc45c3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index 4f74a2336..d8b873d31 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 35020b815..3ef4b336e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 7d113241b..828bee848 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -523,13 +523,7 @@ public class PgpDecryptVerify { // update signature buffer if signature is also present if (signature != null) { - try { - signature.update(buffer, 0, length); - } catch (SignatureException e) { - Log.e(Constants.TAG, "SignatureException -> Not a valid signature!", e); - signatureResultBuilder.validSignature(false); - signature = null; - } + signature.update(buffer, 0, length); } alreadyWritten += length; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 1cf027721..db360f810 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -29,7 +29,6 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.RandomAccessFile; import java.security.SecureRandom; import java.util.regex.Pattern; @@ -68,76 +67,6 @@ public class PgpHelper { } } -// public static final class content { -// public static final int unknown = 0; -// public static final int encrypted_data = 1; -// public static final int keys = 2; -// } -// -// public static int getStreamContent(Context context, InputStream inStream) throws IOException { -// -// InputStream in = PGPUtil.getDecoderStream(inStream); -// PGPObjectFactory pgpF = new PGPObjectFactory(in); -// Object object = pgpF.nextObject(); -// while (object != null) { -// if (object instanceof PGPPublicKeyRing || object instanceof PGPSecretKeyRing) { -// return Id.content.keys; -// } else if (object instanceof PGPEncryptedDataList) { -// return Id.content.encrypted_data; -// } -// object = pgpF.nextObject(); -// } -// -// return Id.content.unknown; -// } - - /** - * Generate a random filename - * - * @param length - * @return - */ - public static String generateRandomFilename(int length) { - SecureRandom random = new SecureRandom(); - - byte bytes[] = new byte[length]; - random.nextBytes(bytes); - String result = ""; - for (int i = 0; i < length; ++i) { - int v = (bytes[i] + 256) % 64; - if (v < 10) { - result += (char) ('0' + v); - } else if (v < 36) { - result += (char) ('A' + v - 10); - } else if (v < 62) { - result += (char) ('a' + v - 36); - } else if (v == 62) { - result += '_'; - } else if (v == 63) { - result += '.'; - } - } - return result; - } - - /** - * Go once through stream to get length of stream. The length is later used to display progress - * when encrypting/decrypting - * - * @param in - * @return - * @throws IOException - */ - public static long getLengthOfStream(InputStream in) throws IOException { - long size = 0; - long n = 0; - byte dummy[] = new byte[0x10000]; - while ((n = in.read(dummy)) > 0) { - size += n; - } - return size; - } - /** * Deletes file securely by overwriting it with random data before deleting it. * <p/> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index f14eacda2..0bc3ac0ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; +import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -43,6 +44,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; public class PgpImportExport { @@ -60,10 +62,14 @@ public class PgpImportExport { private ProviderHelper mProviderHelper; public PgpImportExport(Context context, Progressable progressable) { + this(context, new ProviderHelper(context), progressable); + } + + public PgpImportExport(Context context, ProviderHelper providerHelper, Progressable progressable) { super(); this.mContext = context; this.mProgressable = progressable; - this.mProviderHelper = new ProviderHelper(context); + this.mProviderHelper = providerHelper; } public PgpImportExport(Context context, @@ -93,7 +99,7 @@ public class PgpImportExport { } } - public boolean uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) { + public void uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) throws AddKeyException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = null; try { @@ -103,13 +109,9 @@ public class PgpImportExport { String armoredKey = bos.toString("UTF-8"); server.add(armoredKey); - - return true; } catch (IOException e) { - return false; - } catch (AddKeyException e) { - // TODO: tell the user? - return false; + Log.e(Constants.TAG, "IOException", e); + throw new AddKeyException(); } finally { try { if (aos != null) { @@ -124,20 +126,23 @@ public class PgpImportExport { /** Imports keys from given data. If keyIds is given only those are imported */ public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries) { + return importKeyRings(entries.iterator(), entries.size()); + } + public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num) { updateProgress(R.string.progress_importing, 0, 100); // If there aren't even any keys, do nothing here. - if (entries == null || entries.size() == 0) { + if (entries == null || !entries.hasNext()) { return new ImportKeyResult( - ImportKeyResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0); + ImportKeyResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0, 0); } - int newKeys = 0, oldKeys = 0, badKeys = 0; + int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0; int position = 0; - int progSteps = 100 / entries.size(); - for (ParcelableKeyRing entry : entries) { + double progSteps = 100.0 / num; + for (ParcelableKeyRing entry : new IterableIterator<ParcelableKeyRing>(entries)) { try { UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes()); @@ -157,10 +162,10 @@ public class PgpImportExport { SaveKeyringResult result; if (key.isSecret()) { result = mProviderHelper.saveSecretKeyRing(key, - new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100)); + new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100)); } else { result = mProviderHelper.savePublicKeyRing(key, - new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100)); + new ProgressScaler(mProgressable, (int)(position*progSteps), (int)((position+1)*progSteps), 100)); } if (!result.success()) { badKeys += 1; @@ -168,6 +173,9 @@ public class PgpImportExport { oldKeys += 1; } else { newKeys += 1; + if (key.isSecret()) { + secret += 1; + } } } catch (IOException e) { @@ -204,7 +212,7 @@ public class PgpImportExport { } } - return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys); + return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys, secret); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index 459b80be2..05acb86df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -24,10 +24,16 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves; +import org.spongycastle.bcpg.ECPublicBCPGKey; import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Log; import java.security.DigestException; @@ -37,18 +43,14 @@ import java.util.Locale; public class PgpKeyHelper { - public static String getAlgorithmInfo(int algorithm) { - return getAlgorithmInfo(null, algorithm, 0); - } - - public static String getAlgorithmInfo(Context context, int algorithm) { - return getAlgorithmInfo(context, algorithm, 0); + public static String getAlgorithmInfo(int algorithm, Integer keySize, String oid) { + return getAlgorithmInfo(null, algorithm, keySize, oid); } /** * Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a> */ - public static String getAlgorithmInfo(Context context, int algorithm, int keySize) { + public static String getAlgorithmInfo(Context context, int algorithm, Integer keySize, String oid) { String algorithmStr; switch (algorithm) { @@ -69,10 +71,19 @@ public class PgpKeyHelper { break; } - case PublicKeyAlgorithmTags.ECDSA: + case PublicKeyAlgorithmTags.ECDSA: { + if (oid == null) { + return "ECDSA"; + } + String oidName = PgpKeyHelper.getCurveInfo(context, oid); + return "ECDSA (" + oidName + ")"; + } case PublicKeyAlgorithmTags.ECDH: { - algorithmStr = "ECC"; - break; + if (oid == null) { + return "ECDH"; + } + String oidName = PgpKeyHelper.getCurveInfo(context, oid); + return "ECDH (" + oidName + ")"; } default: { @@ -90,6 +101,106 @@ public class PgpKeyHelper { return algorithmStr; } + public static String getAlgorithmInfo(Algorithm algorithm, Integer keySize, Curve curve) { + return getAlgorithmInfo(null, algorithm, keySize, curve); + } + + /** + * Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a> + */ + public static String getAlgorithmInfo(Context context, Algorithm algorithm, Integer keySize, Curve curve) { + String algorithmStr; + + switch (algorithm) { + case RSA: { + algorithmStr = "RSA"; + break; + } + case DSA: { + algorithmStr = "DSA"; + break; + } + + case ELGAMAL: { + algorithmStr = "ElGamal"; + break; + } + + case ECDSA: { + algorithmStr = "ECDSA"; + if (curve != null) { + algorithmStr += " (" + getCurveInfo(context, curve) + ")"; + } + return algorithmStr; + } + case ECDH: { + algorithmStr = "ECDH"; + if (curve != null) { + algorithmStr += " (" + getCurveInfo(context, curve) + ")"; + } + return algorithmStr; + } + + default: { + if (context != null) { + algorithmStr = context.getResources().getString(R.string.unknown_algorithm); + } else { + algorithmStr = "unknown"; + } + break; + } + } + if (keySize != null && keySize > 0) + return algorithmStr + ", " + keySize + " bit"; + else + return algorithmStr; + } + + // Return name of a curve. These are names, no need for translation + public static String getCurveInfo(Context context, Curve curve) { + switch(curve) { + case NIST_P256: + return "NIST P-256"; + case NIST_P384: + return "NIST P-384"; + case NIST_P521: + return "NIST P-521"; + + /* see SaveKeyringParcel + case BRAINPOOL_P256: + return "Brainpool P-256"; + case BRAINPOOL_P384: + return "Brainpool P-384"; + case BRAINPOOL_P512: + return "Brainpool P-512"; + */ + } + if (context != null) { + return context.getResources().getString(R.string.unknown_algorithm); + } else { + return "unknown"; + } + } + + public static String getCurveInfo(Context context, String oidStr) { + ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(oidStr); + + String name; + name = NISTNamedCurves.getName(oid); + if (name != null) { + return name; + } + name = TeleTrusTNamedCurves.getName(oid); + if (name != null) { + return name; + } + if (context != null) { + return context.getResources().getString(R.string.unknown_algorithm); + } else { + return "unknown"; + } + } + /** * Converts fingerprint to hex * <p/> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index bb692555e..967a7caa9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,12 +20,12 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.bcpg.sig.Features; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.spec.ElGamalParameterSpec; -import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyFlags; import org.spongycastle.openpgp.PGPKeyPair; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; @@ -34,7 +34,6 @@ import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; @@ -53,6 +52,8 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -67,6 +68,7 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.SignatureException; +import java.security.spec.ECGenParameterSpec; import java.util.Arrays; import java.util.Date; import java.util.Iterator; @@ -84,15 +86,45 @@ import java.util.Stack; public class PgpKeyOperation { private Stack<Progressable> mProgress; + // most preferred is first private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{ - SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, - SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5, - SymmetricKeyAlgorithmTags.TRIPLE_DES}; - private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{HashAlgorithmTags.SHA1, - HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160}; + SymmetricKeyAlgorithmTags.AES_256, + SymmetricKeyAlgorithmTags.AES_192, + SymmetricKeyAlgorithmTags.AES_128, + SymmetricKeyAlgorithmTags.CAST5 + }; + private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{ + HashAlgorithmTags.SHA512, + HashAlgorithmTags.SHA384, + HashAlgorithmTags.SHA224, + HashAlgorithmTags.SHA256, + HashAlgorithmTags.RIPEMD160 + }; private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{ - CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, - CompressionAlgorithmTags.ZIP}; + CompressionAlgorithmTags.ZLIB, + CompressionAlgorithmTags.BZIP2, + CompressionAlgorithmTags.ZIP + }; + + /* + * Note: s2kcount is a number between 0 and 0xff that controls the + * number of times to iterate the password hash before use. More + * iterations are useful against offline attacks, as it takes more + * time to check each password. The actual number of iterations is + * rather complex, and also depends on the hash function in use. + * Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give + * you more iterations. As a rough rule of thumb, when using + * SHA256 as the hashing function, 0x10 gives you about 64 + * iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0, + * or about 1 million iterations. The maximum you can go to is + * 0xff, or about 2 million iterations. I'll use 0xc0 as a + * default -- about 130,000 iterations. + * + * http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html + */ + private static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA512; + private static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256; + private static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x60; public PgpKeyOperation(Progressable progress) { super(); @@ -126,31 +158,65 @@ public class PgpKeyOperation { mProgress.peek().setProgress(message, current, 100); } + private ECGenParameterSpec getEccParameterSpec(Curve curve) { + switch (curve) { + case NIST_P256: return new ECGenParameterSpec("P-256"); + case NIST_P384: return new ECGenParameterSpec("P-384"); + case NIST_P521: return new ECGenParameterSpec("P-521"); + + // @see SaveKeyringParcel + // case BRAINPOOL_P256: return new ECGenParameterSpec("brainpoolp256r1"); + // case BRAINPOOL_P384: return new ECGenParameterSpec("brainpoolp384r1"); + // case BRAINPOOL_P512: return new ECGenParameterSpec("brainpoolp512r1"); + } + throw new RuntimeException("Invalid choice! (can't happen)"); + } + /** Creates new secret key. */ - private PGPKeyPair createKey(int algorithmChoice, int keySize, OperationLog log, int indent) { + private PGPKeyPair createKey(SubkeyAdd add, OperationLog log, int indent) { try { - if (keySize < 512) { - log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent); - return null; + // Some safety checks + if (add.mAlgorithm == Algorithm.ECDH || add.mAlgorithm == Algorithm.ECDSA) { + if (add.mCurve == null) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CURVE, indent); + return null; + } + } else { + if (add.mKeySize == null) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_KEYSIZE, indent); + return null; + } + if (add.mKeySize < 512) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent); + return null; + } } int algorithm; KeyPairGenerator keyGen; - switch (algorithmChoice) { - case PublicKeyAlgorithmTags.DSA: { + switch (add.mAlgorithm) { + case DSA: { + if ((add.mFlags & (PGPKeyFlags.CAN_ENCRYPT_COMMS | PGPKeyFlags.CAN_ENCRYPT_STORAGE)) > 0) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_DSA, indent); + return null; + } progress(R.string.progress_generating_dsa, 30); keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); - keyGen.initialize(keySize, new SecureRandom()); + keyGen.initialize(add.mKeySize, new SecureRandom()); algorithm = PGPPublicKey.DSA; break; } - case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: { + case ELGAMAL: { + if ((add.mFlags & (PGPKeyFlags.CAN_SIGN | PGPKeyFlags.CAN_CERTIFY)) > 0) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ELGAMAL, indent); + return null; + } progress(R.string.progress_generating_elgamal, 30); keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME); - BigInteger p = Primes.getBestPrime(keySize); + BigInteger p = Primes.getBestPrime(add.mKeySize); BigInteger g = new BigInteger("2"); ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); @@ -160,15 +226,44 @@ public class PgpKeyOperation { break; } - case PublicKeyAlgorithmTags.RSA_GENERAL: { + case RSA: { progress(R.string.progress_generating_rsa, 30); keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); - keyGen.initialize(keySize, new SecureRandom()); + keyGen.initialize(add.mKeySize, new SecureRandom()); algorithm = PGPPublicKey.RSA_GENERAL; break; } + case ECDSA: { + if ((add.mFlags & (PGPKeyFlags.CAN_ENCRYPT_COMMS | PGPKeyFlags.CAN_ENCRYPT_STORAGE)) > 0) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ECDSA, indent); + return null; + } + progress(R.string.progress_generating_ecdsa, 30); + ECGenParameterSpec ecParamSpec = getEccParameterSpec(add.mCurve); + keyGen = KeyPairGenerator.getInstance("ECDSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); + keyGen.initialize(ecParamSpec, new SecureRandom()); + + algorithm = PGPPublicKey.ECDSA; + break; + } + + case ECDH: { + // make sure there are no sign or certify flags set + if ((add.mFlags & (PGPKeyFlags.CAN_SIGN | PGPKeyFlags.CAN_CERTIFY)) > 0) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_FLAGS_ECDH, indent); + return null; + } + progress(R.string.progress_generating_ecdh, 30); + ECGenParameterSpec ecParamSpec = getEccParameterSpec(add.mCurve); + keyGen = KeyPairGenerator.getInstance("ECDH", Constants.BOUNCY_CASTLE_PROVIDER_NAME); + keyGen.initialize(ecParamSpec, new SecureRandom()); + + algorithm = PGPPublicKey.ECDH; + break; + } + default: { log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent); return null; @@ -181,7 +276,8 @@ public class PgpKeyOperation { } catch(NoSuchProviderException e) { throw new RuntimeException(e); } catch(NoSuchAlgorithmException e) { - throw new RuntimeException(e); + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent); + return null; } catch(InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch(PGPException e) { @@ -218,13 +314,13 @@ public class PgpKeyOperation { return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } - if (add.mAlgorithm == PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT) { - log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent); + if (add.mExpiry == null) { + log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NULL_EXPIRY, indent); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } subProgressPush(10, 30); - PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent); + PGPKeyPair keyPair = createKey(add, log, indent); subProgressPop(); // return null if this failed (an error will already have been logged by createKey) @@ -234,13 +330,16 @@ public class PgpKeyOperation { progress(R.string.progress_building_master_key, 40); - // define hashing and signing algos - PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() - .build().get(HashAlgorithmTags.SHA1); // Build key encrypter and decrypter based on passphrase + PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder() + .build().get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( - PGPEncryptedData.CAST5, sha1Calc) + SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); + + // NOTE: only SHA1 is supported for key checksum calculations. + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() + .build().get(HashAlgorithmTags.SHA1); PGPSecretKey masterSecretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), sha1Calc, true, keyEncryptor); @@ -248,7 +347,7 @@ public class PgpKeyOperation { masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); subProgressPush(50, 100); - return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log); + return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, "", log); } catch (PGPException e) { log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); @@ -314,14 +413,17 @@ public class PgpKeyOperation { // read masterKeyFlags, and use the same as before. // since this is the master key, this contains at least CERTIFY_OTHER - int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER; + PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); + int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER; + long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L : + masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds(); - return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log); + return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log); } private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, - int masterKeyFlags, + int masterKeyFlags, long masterKeyExpiry, SaveKeyringParcel saveParcel, String passphrase, OperationLog log) { @@ -346,177 +448,196 @@ public class PgpKeyOperation { } } - // work on master secret key try { - PGPPublicKey modifiedPublicKey = masterPublicKey; + { // work on master secret key - // 2a. Add certificates for new user ids - subProgressPush(15, 25); - for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) { + PGPPublicKey modifiedPublicKey = masterPublicKey; - progress(R.string.progress_modify_adduid, (i-1) * (100 / saveParcel.mAddUserIds.size())); - String userId = saveParcel.mAddUserIds.get(i); - log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent, userId); + // 2a. Add certificates for new user ids + subProgressPush(15, 25); + for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) { - if (userId.equals("")) { - log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } + progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size())); + String userId = saveParcel.mAddUserIds.get(i); + log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent, userId); - // this operation supersedes all previous binding and revocation certificates, - // so remove those to retain assertions from canonicalization for later operations - @SuppressWarnings("unchecked") - Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId); - if (it != null) { - for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) { - if (cert.getKeyID() != masterPublicKey.getKeyID()) { - // foreign certificate?! error error error - log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION - || cert.getSignatureType() == PGPSignature.NO_CERTIFICATION - || cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION - || cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION - || cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) { - modifiedPublicKey = PGPPublicKey.removeCertification( - modifiedPublicKey, userId, cert); - } + if (userId.equals("")) { + log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } - } - // if it's supposed to be primary, we can do that here as well - boolean isPrimary = saveParcel.mChangePrimaryUserId != null - && userId.equals(saveParcel.mChangePrimaryUserId); - // generate and add new certificate - PGPSignature cert = generateUserIdSignature(masterPrivateKey, - masterPublicKey, userId, isPrimary, masterKeyFlags); - modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); - } - subProgressPop(); + // this operation supersedes all previous binding and revocation certificates, + // so remove those to retain assertions from canonicalization for later operations + @SuppressWarnings("unchecked") + Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId); + if (it != null) { + for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) { + if (cert.getKeyID() != masterPublicKey.getKeyID()) { + // foreign certificate?! error error error + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION + || cert.getSignatureType() == PGPSignature.NO_CERTIFICATION + || cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION + || cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION + || cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) { + modifiedPublicKey = PGPPublicKey.removeCertification( + modifiedPublicKey, userId, cert); + } + } + } - // 2b. Add revocations for revoked user ids - subProgressPush(25, 40); - for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) { + // if it's supposed to be primary, we can do that here as well + boolean isPrimary = saveParcel.mChangePrimaryUserId != null + && userId.equals(saveParcel.mChangePrimaryUserId); + // generate and add new certificate + PGPSignature cert = generateUserIdSignature(masterPrivateKey, + masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + } + subProgressPop(); - progress(R.string.progress_modify_revokeuid, (i-1) * (100 / saveParcel.mRevokeUserIds.size())); - String userId = saveParcel.mRevokeUserIds.get(i); - log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent, userId); + // 2b. Add revocations for revoked user ids + subProgressPush(25, 40); + for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) { + + progress(R.string.progress_modify_revokeuid, (i - 1) * (100 / saveParcel.mRevokeUserIds.size())); + String userId = saveParcel.mRevokeUserIds.get(i); + log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent, userId); + + // Make sure the user id exists (yes these are 10 LoC in Java!) + boolean exists = false; + //noinspection unchecked + for (String uid : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) { + if (userId.equals(uid)) { + exists = true; + break; + } + } + if (!exists) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_REVOKE, indent); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } - // a duplicate revocation will be removed during canonicalization, so no need to - // take care of that here. - PGPSignature cert = generateRevocationSignature(masterPrivateKey, - masterPublicKey, userId); - modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); - } - subProgressPop(); + // a duplicate revocation will be removed during canonicalization, so no need to + // take care of that here. + PGPSignature cert = generateRevocationSignature(masterPrivateKey, + masterPublicKey, userId); + modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); + } + subProgressPop(); - // 3. If primary user id changed, generate new certificates for both old and new - if (saveParcel.mChangePrimaryUserId != null) { - progress(R.string.progress_modify_primaryuid, 40); + // 3. If primary user id changed, generate new certificates for both old and new + if (saveParcel.mChangePrimaryUserId != null) { + progress(R.string.progress_modify_primaryuid, 40); - // keep track if we actually changed one - boolean ok = false; - log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent); - indent += 1; + // keep track if we actually changed one + boolean ok = false; + log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent); + indent += 1; - // we work on the modifiedPublicKey here, to respect new or newly revoked uids - // noinspection unchecked - for (String userId : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) { - boolean isRevoked = false; - PGPSignature currentCert = null; + // we work on the modifiedPublicKey here, to respect new or newly revoked uids // noinspection unchecked - for (PGPSignature cert : new IterableIterator<PGPSignature>( - modifiedPublicKey.getSignaturesForID(userId))) { - if (cert.getKeyID() != masterPublicKey.getKeyID()) { - // foreign certificate?! error error error + for (String userId : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) { + boolean isRevoked = false; + PGPSignature currentCert = null; + // noinspection unchecked + for (PGPSignature cert : new IterableIterator<PGPSignature>( + modifiedPublicKey.getSignaturesForID(userId))) { + if (cert.getKeyID() != masterPublicKey.getKeyID()) { + // foreign certificate?! error error error + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + // we know from canonicalization that if there is any revocation here, it + // is valid and not superseded by a newer certification. + if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) { + isRevoked = true; + continue; + } + // we know from canonicalization that there is only one binding + // certification here, so we can just work with the first one. + if (cert.getSignatureType() == PGPSignature.NO_CERTIFICATION || + cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION || + cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION || + cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) { + currentCert = cert; + } + } + + if (currentCert == null) { + // no certificate found?! error error error log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } - // we know from canonicalization that if there is any revocation here, it - // is valid and not superseded by a newer certification. - if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) { - isRevoked = true; + + // we definitely should not update certifications of revoked keys, so just leave it. + if (isRevoked) { + // revoked user ids cannot be primary! + if (userId.equals(saveParcel.mChangePrimaryUserId)) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } continue; } - // we know from canonicalization that there is only one binding - // certification here, so we can just work with the first one. - if (cert.getSignatureType() == PGPSignature.NO_CERTIFICATION || - cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION || - cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION || - cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) { - currentCert = cert; - } - } - if (currentCert == null) { - // no certificate found?! error error error - log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - - // we definitely should not update certifications of revoked keys, so just leave it. - if (isRevoked) { - // revoked user ids cannot be primary! - if (userId.equals(saveParcel.mChangePrimaryUserId)) { - log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + // if this is~ the/a primary user id + if (currentCert.getHashedSubPackets() != null + && currentCert.getHashedSubPackets().isPrimaryUserID()) { + // if it's the one we want, just leave it as is + if (userId.equals(saveParcel.mChangePrimaryUserId)) { + ok = true; + continue; + } + // otherwise, generate new non-primary certification + log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent); + modifiedPublicKey = PGPPublicKey.removeCertification( + modifiedPublicKey, userId, currentCert); + PGPSignature newCert = generateUserIdSignature( + masterPrivateKey, masterPublicKey, userId, false, + masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + continue; } - continue; - } - // if this is~ the/a primary user id - if (currentCert.getHashedSubPackets() != null - && currentCert.getHashedSubPackets().isPrimaryUserID()) { - // if it's the one we want, just leave it as is + // if we are here, this is not currently a primary user id + + // if it should be if (userId.equals(saveParcel.mChangePrimaryUserId)) { + // add shiny new primary user id certificate + log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_NEW, indent); + modifiedPublicKey = PGPPublicKey.removeCertification( + modifiedPublicKey, userId, currentCert); + PGPSignature newCert = generateUserIdSignature( + masterPrivateKey, masterPublicKey, userId, true, + masterKeyFlags, masterKeyExpiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); ok = true; - continue; } - // otherwise, generate new non-primary certification - log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent); - modifiedPublicKey = PGPPublicKey.removeCertification( - modifiedPublicKey, userId, currentCert); - PGPSignature newCert = generateUserIdSignature( - masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags); - modifiedPublicKey = PGPPublicKey.addCertification( - modifiedPublicKey, userId, newCert); - continue; - } - // if we are here, this is not currently a primary user id - - // if it should be - if (userId.equals(saveParcel.mChangePrimaryUserId)) { - // add shiny new primary user id certificate - log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_NEW, indent); - modifiedPublicKey = PGPPublicKey.removeCertification( - modifiedPublicKey, userId, currentCert); - PGPSignature newCert = generateUserIdSignature( - masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags); - modifiedPublicKey = PGPPublicKey.addCertification( - modifiedPublicKey, userId, newCert); - ok = true; + // user id is not primary and is not supposed to be - nothing to do here. + } - // user id is not primary and is not supposed to be - nothing to do here. + indent -= 1; + if (!ok) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } } - indent -= 1; - - if (!ok) { - log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + // Update the secret key ring + if (modifiedPublicKey != masterPublicKey) { + masterSecretKey = PGPSecretKey.replacePublicKey(masterSecretKey, modifiedPublicKey); + masterPublicKey = modifiedPublicKey; + sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey); } - } - // Update the secret key ring - if (modifiedPublicKey != masterPublicKey) { - masterSecretKey = PGPSecretKey.replacePublicKey(masterSecretKey, modifiedPublicKey); - masterPublicKey = modifiedPublicKey; - sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey); } // 4a. For each subkey change, generate new subkey binding certificate @@ -528,27 +649,47 @@ public class PgpKeyOperation { log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE, indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); - // TODO allow changes in master key? this implies generating new user id certs... - if (change.mKeyId == masterPublicKey.getKeyID()) { - Log.e(Constants.TAG, "changing the master key not supported"); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId); if (sKey == null) { - log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } - PGPPublicKey pKey = sKey.getPublicKey(); // expiry must not be in the past - if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) { - log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, + if (change.mExpiry != null && change.mExpiry != 0 && + new Date(change.mExpiry*1000).before(new Date())) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PAST_EXPIRY, indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } + // if this is the master key, update uid certificates instead + if (change.mKeyId == masterPublicKey.getKeyID()) { + int flags = change.mFlags == null ? masterKeyFlags : change.mFlags; + long expiry = change.mExpiry == null ? masterKeyExpiry : change.mExpiry; + + if ((flags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NO_CERTIFY, indent + 1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + PGPPublicKey pKey = + updateMasterCertificates(masterPrivateKey, masterPublicKey, + flags, expiry, indent, log); + if (pKey == null) { + // error log entry has already been added by updateMasterCertificates itself + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + masterSecretKey = PGPSecretKey.replacePublicKey(masterSecretKey, pKey); + masterPublicKey = pKey; + sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey); + continue; + } + + // otherwise, continue working on the public key + PGPPublicKey pKey = sKey.getPublicKey(); + // keep old flags, or replace with new ones int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags; long expiry; @@ -565,7 +706,7 @@ public class PgpKeyOperation { //noinspection unchecked for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) { // special case: if there is a revocation, don't use expiry from before - if (change.mExpiry == null + if ( (change.mExpiry == null || change.mExpiry == 0L) && sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) { expiry = 0; } @@ -591,7 +732,7 @@ public class PgpKeyOperation { PGPSecretKey sKey = sKR.getSecretKey(revocation); if (sKey == null) { - log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent+1, PgpKeyHelper.convertKeyIdToHex(revocation)); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } @@ -611,10 +752,16 @@ public class PgpKeyOperation { progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size())); SaveKeyringParcel.SubkeyAdd add = saveParcel.mAddSubKeys.get(i); - log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent); + log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent, + PgpKeyHelper.getAlgorithmInfo(add.mAlgorithm, add.mKeySize, add.mCurve) ); + + if (add.mExpiry == null) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } - if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) { - log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1); + if (add.mExpiry > 0L && new Date(add.mExpiry*1000).before(new Date())) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PAST_EXPIRY, indent +1); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } @@ -623,9 +770,10 @@ public class PgpKeyOperation { (i-1) * (100 / saveParcel.mAddSubKeys.size()), i * (100 / saveParcel.mAddSubKeys.size()) ); - PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent); + PGPKeyPair keyPair = createKey(add, log, indent); subProgressPop(); - if(keyPair == null) { + if (keyPair == null) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent +1); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } @@ -633,21 +781,21 @@ public class PgpKeyOperation { PGPPublicKey pKey = keyPair.getPublicKey(); PGPSignature cert = generateSubkeyBindingSignature( masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, - add.mFlags, add.mExpiry == null ? 0 : add.mExpiry); + add.mFlags, add.mExpiry); pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); PGPSecretKey sKey; { - // define hashing and signing algos - PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() - .build().get(HashAlgorithmTags.SHA1); - // Build key encrypter and decrypter based on passphrase + PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder() + .build().get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( - PGPEncryptedData.CAST5, sha1Calc) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); + SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, - sha1Calc, false, keyEncryptor); + // NOTE: only SHA1 is supported for key checksum calculations. + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() + .build().get(HashAlgorithmTags.SHA1); + sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, sha1Calc, false, keyEncryptor); } log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID, @@ -662,21 +810,67 @@ public class PgpKeyOperation { if (saveParcel.mNewPassphrase != null) { progress(R.string.progress_modify_passphrase, 90); log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent); - PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build() - .get(HashAlgorithmTags.SHA1); + indent += 1; + + PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build() + .get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); // Build key encryptor based on new passphrase PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( - PGPEncryptedData.CAST5, sha1Calc) + SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( saveParcel.mNewPassphrase.toCharArray()); - sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew); + // noinspection unchecked + for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { + log.add(LogLevel.DEBUG, LogType.MSG_MF_PASSPHRASE_KEY, indent, + PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID())); + + boolean ok = false; + + try { + // try to set new passphrase + sKey = PGPSecretKey.copyWithNewPassword(sKey, keyDecryptor, keyEncryptorNew); + ok = true; + } catch (PGPException e) { + + // if this is the master key, error! + if (sKey.getKeyID() == masterPublicKey.getKeyID()) { + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // being in here means decrypt failed, likely due to a bad passphrase try + // again with an empty passphrase, maybe we can salvage this + try { + log.add(LogLevel.DEBUG, LogType.MSG_MF_PASSPHRASE_EMPTY_RETRY, indent+1); + PBESecretKeyDecryptor emptyDecryptor = + new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); + sKey = PGPSecretKey.copyWithNewPassword(sKey, emptyDecryptor, keyEncryptorNew); + ok = true; + } catch (PGPException e2) { + // non-fatal but not ok, handled below + } + } + + if (!ok) { + // for a subkey, it's merely a warning + log.add(LogLevel.WARN, LogType.MSG_MF_PASSPHRASE_FAIL, indent+1, + PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID())); + continue; + } + + sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + + } + + indent -= 1; } - // This one must only be thrown by } catch (IOException e) { + Log.e(Constants.TAG, "encountered IOException while modifying key", e); log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_ENCODE, indent+1); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } catch (PGPException e) { @@ -684,6 +878,7 @@ public class PgpKeyOperation { log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent+1); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } catch (SignatureException e) { + Log.e(Constants.TAG, "encountered SignatureException while modifying key", e); log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SIG, indent+1); return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } @@ -694,21 +889,121 @@ public class PgpKeyOperation { } + /** Update all (non-revoked) uid signatures with new flags and expiry time. */ + private static PGPPublicKey updateMasterCertificates( + PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey, + int flags, long expiry, int indent, OperationLog log) + throws PGPException, IOException, SignatureException { + + // keep track if we actually changed one + boolean ok = false; + log.add(LogLevel.DEBUG, LogType.MSG_MF_MASTER, indent); + indent += 1; + + PGPPublicKey modifiedPublicKey = masterPublicKey; + + // we work on the modifiedPublicKey here, to respect new or newly revoked uids + // noinspection unchecked + for (String userId : new IterableIterator<String>(modifiedPublicKey.getUserIDs())) { + boolean isRevoked = false; + PGPSignature currentCert = null; + // noinspection unchecked + for (PGPSignature cert : new IterableIterator<PGPSignature>( + modifiedPublicKey.getSignaturesForID(userId))) { + if (cert.getKeyID() != masterPublicKey.getKeyID()) { + // foreign certificate?! error error error + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); + return null; + } + // we know from canonicalization that if there is any revocation here, it + // is valid and not superseded by a newer certification. + if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) { + isRevoked = true; + continue; + } + // we know from canonicalization that there is only one binding + // certification here, so we can just work with the first one. + if (cert.getSignatureType() == PGPSignature.NO_CERTIFICATION || + cert.getSignatureType() == PGPSignature.CASUAL_CERTIFICATION || + cert.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION || + cert.getSignatureType() == PGPSignature.DEFAULT_CERTIFICATION) { + currentCert = cert; + } + } + + if (currentCert == null) { + // no certificate found?! error error error + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); + return null; + } + + // we definitely should not update certifications of revoked keys, so just leave it. + if (isRevoked) { + continue; + } + + // add shiny new user id certificate + boolean isPrimary = currentCert.getHashedSubPackets() != null && + currentCert.getHashedSubPackets().isPrimaryUserID(); + modifiedPublicKey = PGPPublicKey.removeCertification( + modifiedPublicKey, userId, currentCert); + PGPSignature newCert = generateUserIdSignature( + masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry); + modifiedPublicKey = PGPPublicKey.addCertification( + modifiedPublicKey, userId, newCert); + ok = true; + + } + + if (!ok) { + // might happen, theoretically, if there is a key with no uid.. + log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_MASTER_NONE, indent); + return null; + } + + return modifiedPublicKey; + + } + private static PGPSignature generateUserIdSignature( - PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags) + PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, + int flags, long expiry) throws IOException, PGPException, SignatureException { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - pKey.getAlgorithm(), PGPUtil.SHA1) + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); - subHashedPacketsGen.setSignatureCreationTime(false, new Date()); - subHashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); - subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); - subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); - subHashedPacketsGen.setPrimaryUserID(false, primary); - subHashedPacketsGen.setKeyFlags(false, flags); - sGen.setHashedSubpackets(subHashedPacketsGen.generate()); + + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + { + /* + * From RFC about critical subpackets: + * If a subpacket is encountered that is + * marked critical but is unknown to the evaluating software, the + * evaluator SHOULD consider the signature to be in error. + * An evaluator may "recognize" a subpacket, but not implement it. The + * purpose of the critical bit is to allow the signer to tell an + * evaluator that it would prefer a new, unknown feature to generate an + * error than be ignored. + */ + /* non-critical subpackets: */ + hashedPacketsGen.setPreferredSymmetricAlgorithms(false, PREFERRED_SYMMETRIC_ALGORITHMS); + hashedPacketsGen.setPreferredHashAlgorithms(false, PREFERRED_HASH_ALGORITHMS); + hashedPacketsGen.setPreferredCompressionAlgorithms(false, PREFERRED_COMPRESSION_ALGORITHMS); + hashedPacketsGen.setPrimaryUserID(false, primary); + + /* critical subpackets: we consider those important for a modern pgp implementation */ + hashedPacketsGen.setSignatureCreationTime(true, new Date()); + // Request that senders add the MDC to the message (authenticate unsigned messages) + hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION); + hashedPacketsGen.setKeyFlags(true, flags); + if (expiry > 0) { + hashedPacketsGen.setKeyExpirationTime( + true, expiry - pKey.getCreationTime().getTime() / 1000); + } + } + + sGen.setHashedSubpackets(hashedPacketsGen.generate()); sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); return sGen.generateCertification(userId, pKey); } @@ -717,11 +1012,11 @@ public class PgpKeyOperation { PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) throws IOException, PGPException, SignatureException { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - pKey.getAlgorithm(), PGPUtil.SHA1) + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); - subHashedPacketsGen.setSignatureCreationTime(false, new Date()); + subHashedPacketsGen.setSignatureCreationTime(true, new Date()); sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey); return sGen.generateCertification(userId, pKey); @@ -731,11 +1026,11 @@ public class PgpKeyOperation { PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey) throws IOException, PGPException, SignatureException { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - pKey.getAlgorithm(), PGPUtil.SHA1) + masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); - subHashedPacketsGen.setSignatureCreationTime(false, new Date()); + subHashedPacketsGen.setSignatureCreationTime(true, new Date()); sGen.setHashedSubpackets(subHashedPacketsGen.generate()); // Generate key revocation or subkey revocation, depending on master/subkey-ness if (masterPublicKey.getKeyID() == pKey.getKeyID()) { @@ -765,38 +1060,38 @@ public class PgpKeyOperation { throws IOException, PGPException, SignatureException { // date for signing - Date todayDate = new Date(); + Date creationTime = new Date(); + PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); // If this key can sign, we need a primary key binding signature if ((flags & KeyFlags.SIGN_DATA) > 0) { // cross-certify signing keys PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); - subHashedPacketsGen.setSignatureCreationTime(false, todayDate); + subHashedPacketsGen.setSignatureCreationTime(false, creationTime); PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - pKey.getAlgorithm(), PGPUtil.SHA1) + pKey.getAlgorithm(), HashAlgorithmTags.SHA512) .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, pKey); - unhashedPacketsGen.setEmbeddedSignature(false, certification); + unhashedPacketsGen.setEmbeddedSignature(true, certification); } PGPSignatureSubpacketGenerator hashedPacketsGen; { hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - hashedPacketsGen.setSignatureCreationTime(false, todayDate); - hashedPacketsGen.setKeyFlags(false, flags); - } - - if (expiry > 0) { - long creationTime = pKey.getCreationTime().getTime() / 1000; - hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime); + hashedPacketsGen.setSignatureCreationTime(true, creationTime); + hashedPacketsGen.setKeyFlags(true, flags); + if (expiry > 0) { + hashedPacketsGen.setKeyExpirationTime(true, + expiry - pKey.getCreationTime().getTime() / 1000); + } } PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - pKey.getAlgorithm(), PGPUtil.SHA1) + masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index 3fe535f65..2e4eafe41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -70,7 +70,7 @@ public class PgpSignEncrypt { private long mSignatureMasterKeyId; private int mSignatureHashAlgorithm; private String mSignaturePassphrase; - private boolean mEncryptToSigner; + private long mAdditionalEncryptId; private boolean mCleartextInput; private String mOriginalFilename; @@ -103,7 +103,7 @@ public class PgpSignEncrypt { this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId; this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm; this.mSignaturePassphrase = builder.mSignaturePassphrase; - this.mEncryptToSigner = builder.mEncryptToSigner; + this.mAdditionalEncryptId = builder.mAdditionalEncryptId; this.mCleartextInput = builder.mCleartextInput; this.mNfcSignedHash = builder.mNfcSignedHash; this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp; @@ -127,7 +127,7 @@ public class PgpSignEncrypt { private long mSignatureMasterKeyId = Constants.key.none; private int mSignatureHashAlgorithm = 0; private String mSignaturePassphrase = null; - private boolean mEncryptToSigner = false; + private long mAdditionalEncryptId = Constants.key.none; private boolean mCleartextInput = false; private String mOriginalFilename = ""; private byte[] mNfcSignedHash = null; @@ -175,7 +175,7 @@ public class PgpSignEncrypt { } public Builder setSignatureMasterKeyId(long signatureMasterKeyId) { - this.mSignatureMasterKeyId = signatureMasterKeyId; + mSignatureMasterKeyId = signatureMasterKeyId; return this; } @@ -192,11 +192,11 @@ public class PgpSignEncrypt { /** * Also encrypt with the signing keyring * - * @param encryptToSigner + * @param additionalEncryptId * @return */ - public Builder setEncryptToSigner(boolean encryptToSigner) { - mEncryptToSigner = encryptToSigner; + public Builder setAdditionalEncryptId(long additionalEncryptId) { + mAdditionalEncryptId = additionalEncryptId; return this; } @@ -288,10 +288,10 @@ public class PgpSignEncrypt { + "\nenableCompression:" + enableCompression + "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput); - // add signature key id to encryption ids (self-encrypt) - if (enableEncryption && enableSignature && mEncryptToSigner) { + // add additional key id to encryption ids (mostly to do self-encryption) + if (enableEncryption && mAdditionalEncryptId != Constants.key.none) { mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1); - mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mSignatureMasterKeyId; + mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mAdditionalEncryptId; } ArmoredOutputStream armorOut = null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 73a51942d..f00383e0f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,6 +45,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.Iterator; @@ -55,7 +58,8 @@ import java.util.TreeSet; * This class and its relatives UncachedPublicKey and UncachedSecretKey are * used to move around pgp key rings in non crypto related (UI, mostly) code. * It should be used for simple inspection only until it saved in the database, - * all actual crypto operations should work with WrappedKeyRings exclusively. + * all actual crypto operations should work with CanonicalizedKeyRings + * exclusively. * * This class is also special in that it can hold either the PGPPublicKeyRing * or PGPSecretKeyRing derivate of the PGPKeyRing class, since these are @@ -118,6 +122,10 @@ public class UncachedKeyRing { return mRing.getPublicKey().getFingerprint(); } + public int getVersion() { + return mRing.getPublicKey().getVersion(); + } + public static UncachedKeyRing decodeFromData(byte[] data) throws PgpGeneralException, IOException { @@ -211,8 +219,7 @@ public class UncachedKeyRing { aos.close(); } - /** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be - * applied to public keyrings only. + /** "Canonicalizes" a public key, removing inconsistencies in the process. * * More specifically: * - Remove all non-verifying self-certificates @@ -229,9 +236,9 @@ public class UncachedKeyRing { * - If the key is a secret key, remove all certificates by foreign keys * - If no valid user id remains, log an error and return null * - * This operation writes an OperationLog which can be used as part of a OperationResultParcel. + * This operation writes an OperationLog which can be used as part of an OperationResultParcel. * - * @return A canonicalized key, or null on fatal error + * @return A canonicalized key, or null on fatal error (log will include a message in this case) * */ @SuppressWarnings("ConstantConditions") @@ -241,6 +248,12 @@ public class UncachedKeyRing { indent, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())); indent += 1; + // do not accept v3 keys + if (getVersion() <= 3) { + log.add(LogLevel.ERROR, LogType.MSG_KC_V3_KEY, indent); + return null; + } + final Date now = new Date(); int redundantCerts = 0, badCerts = 0; @@ -259,13 +272,12 @@ public class UncachedKeyRing { for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) { int type = zert.getSignatureType(); - // Disregard certifications on user ids, we will deal with those later + // These should most definitely not be here... if (type == PGPSignature.NO_CERTIFICATION || type == PGPSignature.DEFAULT_CERTIFICATION || type == PGPSignature.CASUAL_CERTIFICATION || type == PGPSignature.POSITIVE_CERTIFICATION || type == PGPSignature.CERTIFICATION_REVOCATION) { - // These should not be here... log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; @@ -328,7 +340,17 @@ public class UncachedKeyRing { } } + ArrayList<String> processedUserIds = new ArrayList<String>(); for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) { + // check for duplicate user ids + if (processedUserIds.contains(userId)) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_DUP, + indent, userId); + // strip out the first found user id with this name + modified = PGPPublicKey.removeCertification(modified, userId); + } + processedUserIds.add(userId); + PGPSignature selfCert = null; revocation = null; @@ -405,13 +427,13 @@ public class UncachedKeyRing { if (selfCert == null) { selfCert = zert; } else if (selfCert.getCreationTime().before(cert.getCreationTime())) { - log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP, indent, userId); modified = PGPPublicKey.removeCertification(modified, userId, selfCert); redundantCerts += 1; selfCert = zert; } else { - log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP, indent, userId); modified = PGPPublicKey.removeCertification(modified, userId, zert); redundantCerts += 1; @@ -474,6 +496,10 @@ public class UncachedKeyRing { // Replace modified key in the keyring ring = replacePublicKey(ring, modified); + if (ring == null) { + log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent); + return null; + } indent -= 1; } @@ -580,8 +606,8 @@ public class UncachedKeyRing { } - // if we already have a cert, and this one is not newer: skip it - if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) { + // if we already have a cert, and this one is older: skip it + if (selfCert != null && cert.getCreationTime().before(selfCert.getCreationTime())) { log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_DUP, indent); redundantCerts += 1; continue; @@ -641,6 +667,10 @@ public class UncachedKeyRing { } // replace pubkey in keyring ring = replacePublicKey(ring, modified); + if (ring == null) { + log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent); + return null; + } indent -= 1; } @@ -681,8 +711,9 @@ public class UncachedKeyRing { long masterKeyId = other.getMasterKeyId(); - if (getMasterKeyId() != masterKeyId) { - log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, indent); + if (getMasterKeyId() != masterKeyId + || !Arrays.equals(getFingerprint(), other.getFingerprint())) { + log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_HETEROGENEOUS, indent); return null; } @@ -729,6 +760,10 @@ public class UncachedKeyRing { } else { // otherwise, just insert the public key result = replacePublicKey(result, key); + if (result == null) { + log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent); + return null; + } } continue; } @@ -757,6 +792,10 @@ public class UncachedKeyRing { if (!key.isMasterKey()) { if (modified != resultKey) { result = replacePublicKey(result, modified); + if (result == null) { + log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent); + return null; + } } continue; } @@ -781,6 +820,10 @@ public class UncachedKeyRing { // If anything changed, save the updated (sub)key if (modified != resultKey) { result = replacePublicKey(result, modified); + if (result == null) { + log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_SECRET_DUMMY, indent); + return null; + } } } @@ -795,7 +838,7 @@ public class UncachedKeyRing { return new UncachedKeyRing(result); } catch (IOException e) { - log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, indent); + log.add(LogLevel.ERROR, LogType.MSG_MG_ERROR_ENCODE, indent); return null; } @@ -826,16 +869,19 @@ public class UncachedKeyRing { */ private static PGPKeyRing replacePublicKey(PGPKeyRing ring, PGPPublicKey key) { if (ring instanceof PGPPublicKeyRing) { - return PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) ring, key); - } - PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring; - PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID()); - // TODO generate secret key with S2K dummy, if none exists! for now, just die. - if (sKey == null) { - throw new RuntimeException("dummy secret key generation not yet implemented"); + PGPPublicKeyRing pubRing = (PGPPublicKeyRing) ring; + return PGPPublicKeyRing.insertPublicKey(pubRing, key); + } else { + PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring; + PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID()); + // TODO generate secret key with S2K dummy, if none exists! + if (sKey == null) { + Log.e(Constants.TAG, "dummy secret key generation not yet implemented"); + return null; + } + sKey = PGPSecretKey.replacePublicKey(sKey, key); + return PGPSecretKeyRing.insertSecretKey(secRing, sKey); } - sKey = PGPSecretKey.replacePublicKey(sKey, key); - return PGPSecretKeyRing.insertSecretKey(secRing, sKey); } /** This method removes a subkey in a keyring. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 341ca6d04..c7a8bb1d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +18,10 @@ package org.sufficientlysecure.keychain.pgp; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves; +import org.spongycastle.bcpg.ECPublicBCPGKey; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSignature; @@ -93,10 +98,23 @@ public class UncachedPublicKey { return mPublicKey.getAlgorithm(); } - public int getBitStrength() { + public Integer getBitStrength() { + if (isEC()) { + return null; + } return mPublicKey.getBitStrength(); } + public String getCurveOid() { + if ( ! isEC()) { + return null; + } + if ( ! (mPublicKey.getPublicKeyPacket().getKey() instanceof ECPublicBCPGKey)) { + return null; + } + return ((ECPublicBCPGKey) mPublicKey.getPublicKeyPacket().getKey()).getCurveOID().getId(); + } + /** Returns the primary user id, as indicated by the public key's self certificates. * * This is an expensive operation, since potentially a lot of certificates (and revocations) @@ -185,6 +203,10 @@ public class UncachedPublicKey { return getAlgorithm() == PGPPublicKey.DSA; } + public boolean isEC() { + return getAlgorithm() == PGPPublicKey.ECDH || getAlgorithm() == PGPPublicKey.ECDSA; + } + @SuppressWarnings("unchecked") // TODO make this safe public int getKeyUsage() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java index 8dc28c2b3..4dfd93289 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedSecretKey.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index ebd110dc5..f24259ba7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -95,9 +96,6 @@ public class WrappedSignature { } catch (PGPException e) { // no matter Log.e(Constants.TAG, "exception reading embedded signatures", e); - } catch (IOException e) { - // no matter - Log.e(Constants.TAG, "exception reading embedded signatures", e); } return sigs; } @@ -149,27 +147,17 @@ public class WrappedSignature { } } - public void update(byte[] data, int offset, int length) throws PgpGeneralException { - try { - mSig.update(data, offset, length); - } catch(SignatureException e) { - throw new PgpGeneralException(e); - } + public void update(byte[] data, int offset, int length) { + mSig.update(data, offset, length); } - public void update(byte data) throws PgpGeneralException { - try { - mSig.update(data); - } catch(SignatureException e) { - throw new PgpGeneralException(e); - } + public void update(byte data) { + mSig.update(data); } public boolean verify() throws PgpGeneralException { try { return mSig.verify(); - } catch(SignatureException e) { - throw new PgpGeneralException(e); } catch(PGPException e) { throw new PgpGeneralException(e); } @@ -178,8 +166,6 @@ public class WrappedSignature { boolean verifySignature(PGPPublicKey key) throws PgpGeneralException { try { return mSig.verifyCertification(key); - } catch (SignatureException e) { - throw new PgpGeneralException("Sign!", e); } catch (PGPException e) { throw new PgpGeneralException("Error!", e); } @@ -188,8 +174,6 @@ public class WrappedSignature { boolean verifySignature(PGPPublicKey masterKey, PGPPublicKey subKey) throws PgpGeneralException { try { return mSig.verifyCertification(masterKey, subKey); - } catch (SignatureException e) { - throw new PgpGeneralException("Sign!", e); } catch (PGPException e) { throw new PgpGeneralException("Error!", e); } @@ -198,8 +182,6 @@ public class WrappedSignature { boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { try { return mSig.verifyCertification(uid, key); - } catch (SignatureException e) { - throw new PgpGeneralException("Error!", e); } catch (PGPException e) { throw new PgpGeneralException("Error!", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java index 3700b4c34..aa0d86bb3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index e076fd9cc..21f0dddf6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index dd59f8603..4f6e878b4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,6 +39,7 @@ public class KeychainContract { String FINGERPRINT = "fingerprint"; String KEY_SIZE = "key_size"; + String KEY_CURVE_OID = "key_curve_oid"; String CAN_SIGN = "can_sign"; String CAN_ENCRYPT = "can_encrypt"; String CAN_CERTIFY = "can_certify"; @@ -111,6 +113,7 @@ public class KeychainContract { public static final String HAS_ANY_SECRET = "has_any_secret"; public static final String HAS_ENCRYPT = "has_encrypt"; public static final String HAS_SIGN = "has_sign"; + public static final String HAS_CERTIFY = "has_certify"; public static final String PUBKEY_DATA = "pubkey_data"; public static final String PRIVKEY_DATA = "privkey_data"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 3a859f505..0bb43d47f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,7 +52,7 @@ import java.io.IOException; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 2; + private static final int DATABASE_VERSION = 3; static Boolean apgHack = false; public interface Tables { @@ -85,6 +86,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + KeysColumns.KEY_ID + " INTEGER, " + KeysColumns.KEY_SIZE + " INTEGER, " + + KeysColumns.KEY_CURVE_OID + " TEXT, " + KeysColumns.ALGORITHM + " INTEGER, " + KeysColumns.FINGERPRINT + " BLOB, " @@ -201,13 +203,20 @@ public class KeychainDatabase extends SQLiteOpenHelper { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion == 1) { - // add has_secret for all who are upgrading from a beta version - try { - db.execSQL("ALTER TABLE keys ADD COLUMN has_secret BOOLEAN"); - } catch (Exception e) { - // never mind, the column probably already existed - } + // add has_secret for all who are upgrading from a beta version + switch (oldVersion) { + case 1: + try { + db.execSQL("ALTER TABLE keys ADD COLUMN has_secret BOOLEAN"); + } catch(Exception e){ + // never mind, the column probably already existed + } + case 2: + try { + db.execSQL("ALTER TABLE keys ADD COLUMN " + KeysColumns.KEY_CURVE_OID + " TEXT"); + } catch(Exception e){ + // never mind, the column probably already existed + } } } @@ -227,7 +236,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { if (db.equals("apg.db")) { hasApgDb = true; } else if (db.equals("apg_old.db")) { - Log.d(Constants.TAG, "Found apg_old.db"); + Log.d(Constants.TAG, "Found apg_old.db, delete it!"); + context.getDatabasePath("apg_old.db").delete(); } } } @@ -310,9 +320,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { } } - // Move to a different file (but don't delete, just to be safe) - Log.d(Constants.TAG, "All done - moving apg.db to apg_old.db"); - context.getDatabasePath("apg.db").renameTo(context.getDatabasePath("apg_old.db")); + // delete old database + context.getDatabasePath("apg.db").delete(); } private static void copy(File in, File out) throws IOException { @@ -349,4 +358,9 @@ public class KeychainDatabase extends SQLiteOpenHelper { copy(in, out); } + // DANGEROUS, use in test code ONLY! + public void clearDatabase() { + getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index c914cb5b7..f6df4a3eb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -1,6 +1,7 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -245,6 +246,7 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID); projectionMap.put(KeyRings.KEY_SIZE, Tables.KEYS + "." + Keys.KEY_SIZE); + projectionMap.put(KeyRings.KEY_CURVE_OID, Tables.KEYS + "." + Keys.KEY_CURVE_OID); projectionMap.put(KeyRings.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED); projectionMap.put(KeyRings.CAN_CERTIFY, Tables.KEYS + "." + Keys.CAN_CERTIFY); projectionMap.put(KeyRings.CAN_ENCRYPT, Tables.KEYS + "." + Keys.CAN_ENCRYPT); @@ -271,6 +273,8 @@ public class KeychainProvider extends ContentProvider { "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); projectionMap.put(KeyRings.HAS_SIGN, "kS." + Keys.KEY_ID + " AS " + KeyRings.HAS_SIGN); + projectionMap.put(KeyRings.HAS_CERTIFY, + "kC." + Keys.KEY_ID + " AS " + KeyRings.HAS_CERTIFY); projectionMap.put(KeyRings.IS_EXPIRED, "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY + " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED); @@ -324,6 +328,15 @@ public class KeychainProvider extends ContentProvider { + " AND ( kS." + Keys.EXPIRY + " IS NULL OR kS." + Keys.EXPIRY + " >= " + new Date().getTime() / 1000 + " )" + ")" : "") + + (plist.contains(KeyRings.HAS_CERTIFY) ? + " LEFT JOIN " + Tables.KEYS + " AS kC ON (" + +"kC." + Keys.MASTER_KEY_ID + + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND kC." + Keys.IS_REVOKED + " = 0" + + " AND kC." + Keys.CAN_CERTIFY + " = 1" + + " AND ( kC." + Keys.EXPIRY + " IS NULL OR kC." + Keys.EXPIRY + + " >= " + new Date().getTime() / 1000 + " )" + + ")" : "") ); qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0"); // in case there are multiple verifying certificates @@ -400,6 +413,7 @@ public class KeychainProvider extends ContentProvider { 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.KEY_CURVE_OID, Keys.KEY_CURVE_OID); projectionMap.put(Keys.IS_REVOKED, Keys.IS_REVOKED); projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY); projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); @@ -674,6 +688,11 @@ public class KeychainProvider extends ContentProvider { final int match = mUriMatcher.match(uri); switch (match) { + // dangerous + case KEY_RINGS_UNIFIED: { + count = db.delete(Tables.KEY_RINGS_PUBLIC, null, null); + break; + } case KEY_RING_PUBLIC: { @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index a13bb9c98..bb095c340 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,12 +29,16 @@ import android.os.RemoteException; import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.Preferences; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.NullProgressable; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.pgp.PgpImportExport; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -51,9 +56,12 @@ import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; +import org.sufficientlysecure.keychain.service.OperationResults.ConsolidateResult; import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; +import org.sufficientlysecure.keychain.util.FileImportCache; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressFixedScaler; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -63,6 +71,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -86,6 +95,10 @@ public class ProviderHelper { this(context, new OperationLog(), 0); } + public ProviderHelper(Context context, OperationLog log) { + this(context, log, 0); + } + public ProviderHelper(Context context, OperationLog log, int indent) { mContext = context; mContentResolver = context.getContentResolver(); @@ -93,14 +106,6 @@ public class ProviderHelper { mIndent = indent; } - public void resetLog() { - if(mLog != null) { - // Start a new log (leaving the old one intact) - mLog = new OperationLog(); - mIndent = 0; - } - } - public OperationLog getLog() { return mLog; } @@ -322,6 +327,7 @@ public class ProviderHelper { values.put(Keys.KEY_ID, key.getKeyId()); values.put(Keys.KEY_SIZE, key.getBitStrength()); + values.put(Keys.KEY_CURVE_OID, key.getCurveOid()); values.put(Keys.ALGORITHM, key.getAlgorithm()); values.put(Keys.FINGERPRINT, key.getFingerprint()); @@ -644,7 +650,7 @@ public class ProviderHelper { if (publicRing.isSecret()) { log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET); - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } CanonicalizedPublicKeyRing canPublicRing; @@ -658,20 +664,20 @@ public class ProviderHelper { // If this is null, there is an error in the log so we can just return if (publicRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } // Canonicalize this keyring, to assert a number of assumptions made about it. canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); if (canPublicRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } // Early breakout if nothing changed if (Arrays.hashCode(publicRing.getEncoded()) == Arrays.hashCode(oldPublicRing.getEncoded())) { log(LogLevel.OK, LogType.MSG_IP_SUCCESS_IDENTICAL); - return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog); + return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, null); } } catch (NotFoundException e) { // Not an issue, just means we are dealing with a new keyring. @@ -679,7 +685,7 @@ public class ProviderHelper { // Canonicalize this keyring, to assert a number of assumptions made about it. canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); if (canPublicRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } } @@ -692,12 +698,12 @@ public class ProviderHelper { // Merge data from new public ring into secret one secretRing = secretRing.merge(publicRing, mLog, mIndent); if (secretRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } // This has always been a secret key ring, this is a safe cast canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent); if (canSecretRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } } catch (NotFoundException e) { @@ -716,11 +722,11 @@ public class ProviderHelper { } } - return new SaveKeyringResult(result, mLog); + return new SaveKeyringResult(result, mLog, canSecretRing); } catch (IOException e) { log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC); - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } finally { mIndent -= 1; } @@ -736,7 +742,7 @@ public class ProviderHelper { if ( ! secretRing.isSecret()) { log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC); - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } CanonicalizedSecretKeyRing canSecretRing; @@ -750,14 +756,14 @@ public class ProviderHelper { // If this is null, there is an error in the log so we can just return if (secretRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } // Canonicalize this keyring, to assert a number of assumptions made about it. // This is a safe cast, because we made sure this is a secret ring above canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent); if (canSecretRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } // Early breakout if nothing changed @@ -765,7 +771,7 @@ public class ProviderHelper { == Arrays.hashCode(oldSecretRing.getEncoded())) { log(LogLevel.OK, LogType.MSG_IS_SUCCESS_IDENTICAL, PgpKeyHelper.convertKeyIdToHex(masterKeyId) ); - return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog); + return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, null); } } catch (NotFoundException e) { // Not an issue, just means we are dealing with a new keyring @@ -774,7 +780,7 @@ public class ProviderHelper { // This is a safe cast, because we made sure this is a secret ring above canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent); if (canSecretRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } } @@ -787,7 +793,7 @@ public class ProviderHelper { // Merge data from new secret ring into public one publicRing = oldPublicRing.merge(secretRing, mLog, mIndent); if (publicRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } } catch (NotFoundException e) { @@ -797,30 +803,289 @@ public class ProviderHelper { CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent); if (canPublicRing == null) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } int result; result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true); if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) { - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100); result = saveCanonicalizedSecretKeyRing(canSecretRing); - return new SaveKeyringResult(result, mLog); + return new SaveKeyringResult(result, mLog, canSecretRing); } catch (IOException e) { log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC); - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null); } finally { mIndent -= 1; } } + public ConsolidateResult consolidateDatabaseStep1(Progressable progress) { + + // 1a. fetch all secret keyrings into a cache file + log(LogLevel.START, LogType.MSG_CON); + mIndent += 1; + + progress.setProgress(R.string.progress_con_saving, 0, 100); + + try { + + log(LogLevel.DEBUG, LogType.MSG_CON_SAVE_SECRET); + mIndent += 1; + + final Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[]{ + KeyRings.PRIVKEY_DATA, KeyRings.FINGERPRINT, KeyRings.HAS_ANY_SECRET + }, KeyRings.HAS_ANY_SECRET + " = 1", null, null); + + if (cursor == null || !cursor.moveToFirst()) { + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_DB); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } + + Preferences.getPreferences(mContext).setCachedConsolidateNumSecrets(cursor.getCount()); + + FileImportCache<ParcelableKeyRing> cache = + new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl"); + cache.writeCache(new Iterator<ParcelableKeyRing>() { + ParcelableKeyRing ring; + + @Override + public boolean hasNext() { + if (ring != null) { + return true; + } + if (cursor.isAfterLast()) { + return false; + } + ring = new ParcelableKeyRing(cursor.getBlob(0), + PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1))); + cursor.moveToNext(); + return true; + } + + @Override + public ParcelableKeyRing next() { + try { + return ring; + } finally { + ring = null; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }); + + } catch (IOException e) { + Log.e(Constants.TAG, "error saving secret", e); + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_IO_SECRET); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } finally { + mIndent -= 1; + } + + progress.setProgress(R.string.progress_con_saving, 3, 100); + + // 1b. fetch all public keyrings into a cache file + try { + + log(LogLevel.DEBUG, LogType.MSG_CON_SAVE_PUBLIC); + mIndent += 1; + + final Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[]{ + KeyRings.PUBKEY_DATA, KeyRings.FINGERPRINT + }, null, null, null); + + if (cursor == null || !cursor.moveToFirst()) { + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_DB); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } + + Preferences.getPreferences(mContext).setCachedConsolidateNumPublics(cursor.getCount()); + + FileImportCache<ParcelableKeyRing> cache = + new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl"); + cache.writeCache(new Iterator<ParcelableKeyRing>() { + ParcelableKeyRing ring; + + @Override + public boolean hasNext() { + if (ring != null) { + return true; + } + if (cursor.isAfterLast()) { + return false; + } + ring = new ParcelableKeyRing(cursor.getBlob(0), + PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1))); + cursor.moveToNext(); + return true; + } + + @Override + public ParcelableKeyRing next() { + try { + return ring; + } finally { + ring = null; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }); + + } catch (IOException e) { + Log.e(Constants.TAG, "error saving public", e); + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_IO_PUBLIC); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } finally { + mIndent -= 1; + } + + log(LogLevel.INFO, LogType.MSG_CON_CRITICAL_IN); + Preferences.getPreferences(mContext).setCachedConsolidate(true); + + return consolidateDatabaseStep2(progress, false); + } + + public ConsolidateResult consolidateDatabaseStep2(Progressable progress) { + return consolidateDatabaseStep2(progress, true); + } + + private static boolean mConsolidateCritical = false; + + private ConsolidateResult consolidateDatabaseStep2(Progressable progress, boolean recovery) { + + synchronized (ProviderHelper.class) { + if (mConsolidateCritical) { + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_CONCURRENT); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } + mConsolidateCritical = true; + } + + try { + Preferences prefs = Preferences.getPreferences(mContext); + + // Set flag that we have a cached consolidation here + int numSecrets = prefs.getCachedConsolidateNumSecrets(); + int numPublics = prefs.getCachedConsolidateNumPublics(); + + if (recovery) { + if (numSecrets >= 0 && numPublics >= 0) { + log(LogLevel.START, LogType.MSG_CON_RECOVER, numSecrets, numPublics); + } else { + log(LogLevel.START, LogType.MSG_CON_RECOVER_UNKNOWN); + } + mIndent += 1; + } + + if (!prefs.getCachedConsolidate()) { + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_BAD_STATE); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } + + // 2. wipe database (IT'S DANGEROUS) + log(LogLevel.DEBUG, LogType.MSG_CON_DB_CLEAR); + mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null); + + FileImportCache<ParcelableKeyRing> cacheSecret = + new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl"); + FileImportCache<ParcelableKeyRing> cachePublic = + new FileImportCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl"); + + // 3. Re-Import secret keyrings from cache + if (numSecrets > 0) try { + log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_SECRET, numSecrets); + mIndent += 1; + + new PgpImportExport(mContext, this, + new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport)) + .importKeyRings(cacheSecret.readCache(false), numSecrets); + } catch (IOException e) { + Log.e(Constants.TAG, "error importing secret", e); + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_SECRET); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } finally { + mIndent -= 1; + } + else { + log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_SECRET_SKIP); + } + + // 4. Re-Import public keyrings from cache + if (numPublics > 0) try { + log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_PUBLIC, numPublics); + mIndent += 1; + + new PgpImportExport(mContext, this, + new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport)) + .importKeyRings(cachePublic.readCache(false), numPublics); + } catch (IOException e) { + Log.e(Constants.TAG, "error importing public", e); + log(LogLevel.ERROR, LogType.MSG_CON_ERROR_PUBLIC); + return new ConsolidateResult(ConsolidateResult.RESULT_ERROR, mLog); + } finally { + mIndent -= 1; + } + else { + log(LogLevel.DEBUG, LogType.MSG_CON_REIMPORT_PUBLIC_SKIP); + } + + log(LogLevel.INFO, LogType.MSG_CON_CRITICAL_OUT); + Preferences.getPreferences(mContext).setCachedConsolidate(false); + + // 5. Delete caches + try { + log(LogLevel.DEBUG, LogType.MSG_CON_DELETE_SECRET); + mIndent += 1; + cacheSecret.delete(); + } catch (IOException e) { + // doesn't /really/ matter + Log.e(Constants.TAG, "IOException during delete of secret cache", e); + log(LogLevel.WARN, LogType.MSG_CON_WARN_DELETE_SECRET); + } finally { + mIndent -= 1; + } + + try { + log(LogLevel.DEBUG, LogType.MSG_CON_DELETE_PUBLIC); + mIndent += 1; + cachePublic.delete(); + } catch (IOException e) { + // doesn't /really/ matter + Log.e(Constants.TAG, "IOException during deletion of public cache", e); + log(LogLevel.WARN, LogType.MSG_CON_WARN_DELETE_PUBLIC); + } finally { + mIndent -= 1; + } + + progress.setProgress(100, 100); + log(LogLevel.OK, LogType.MSG_CON_SUCCESS); + mIndent -= 1; + + return new ConsolidateResult(ConsolidateResult.RESULT_OK, mLog); + + } finally { + mConsolidateCritical = false; + } + + } + /** * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ @@ -863,9 +1128,7 @@ public class ProviderHelper { ByteArrayOutputStream bos = new ByteArrayOutputStream(); String version = PgpHelper.getVersionForHeader(mContext); - if (version != null) { - keyRing.encodeArmored(bos, version); - } + keyRing.encodeArmored(bos, version); String armoredKey = bos.toString("UTF-8"); Log.d(Constants.TAG, "armoredKey:" + armoredKey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java index 87c0cc0a6..a65d222da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 44d37b926..20dfac36d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -23,6 +23,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.text.TextUtils; import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.OpenPgpMetadata; @@ -38,6 +39,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; 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.ApiAccounts; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -56,11 +58,16 @@ import java.util.Set; public class OpenPgpService extends RemoteService { - static final String[] KEYRING_PROJECTION = - new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - }; + static final String[] EMAIL_SEARCH_PROJECTION = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.IS_EXPIRED, + KeyRings.IS_REVOKED, + }; + + // do not pre-select revoked or expired keys + static final String EMAIL_SEARCH_WHERE = KeychainContract.KeyRings.IS_REVOKED + " = 0 AND " + + KeychainContract.KeyRings.IS_EXPIRED + " = 0"; /** * Search database for key ids based on emails. @@ -69,52 +76,61 @@ public class OpenPgpService extends RemoteService { * @return */ private Intent getKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { - // find key ids to given emails in database - ArrayList<Long> keyIds = new ArrayList<Long>(); - + boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0); boolean missingUserIdsCheck = false; boolean duplicateUserIdsCheck = false; + + ArrayList<Long> keyIds = new ArrayList<Long>(); ArrayList<String> missingUserIds = new ArrayList<String>(); ArrayList<String> duplicateUserIds = new ArrayList<String>(); - - for (String email : encryptionUserIds) { - Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email); - Cursor cursor = getContentResolver().query(uri, KEYRING_PROJECTION, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID)); - keyIds.add(id); - } else { - missingUserIdsCheck = true; - missingUserIds.add(email); - Log.d(Constants.TAG, "user id missing"); - } - if (cursor != null && cursor.moveToNext()) { - duplicateUserIdsCheck = true; - duplicateUserIds.add(email); - Log.d(Constants.TAG, "more than one user id with the same email"); - } - } finally { - if (cursor != null) { - cursor.close(); + if (!noUserIdsCheck) { + for (String email : encryptionUserIds) { + // try to find the key for this specific email + Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email); + Cursor cursor = getContentResolver().query(uri, EMAIL_SEARCH_PROJECTION, EMAIL_SEARCH_WHERE, null, null); + try { + // result should be one entry containing the key id + if (cursor != null && cursor.moveToFirst()) { + long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID)); + keyIds.add(id); + } else { + missingUserIdsCheck = true; + missingUserIds.add(email); + Log.d(Constants.TAG, "user id missing"); + } + // another entry for this email -> too keys with the same email inside user id + if (cursor != null && cursor.moveToNext()) { + duplicateUserIdsCheck = true; + duplicateUserIds.add(email); + + // also pre-select + long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID)); + keyIds.add(id); + Log.d(Constants.TAG, "more than one user id with the same email"); + } + } finally { + if (cursor != null) { + cursor.close(); + } } } } - // convert to long[] + // convert ArrayList<Long> to long[] long[] keyIdsArray = new long[keyIds.size()]; for (int i = 0; i < keyIdsArray.length; i++) { keyIdsArray[i] = keyIds.get(i); } - // allow the user to verify pub key selection - if (missingUserIdsCheck || duplicateUserIdsCheck) { - // build PendingIntent + if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) { + // allow the user to verify pub key selection + 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_NO_USER_IDS_CHECK, noUserIdsCheck); intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds); - intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, duplicateUserIds); + intent.putExtra(RemoteServiceActivity.EXTRA_DUPLICATE_USER_IDS, duplicateUserIds); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0, @@ -126,16 +142,18 @@ public class OpenPgpService extends RemoteService { result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); return result; - } + } else { + // everything was easy, we have exactly one key for every email - if (keyIdsArray.length == 0) { - return null; - } + if (keyIdsArray.length == 0) { + Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!"); + } - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } } private Intent getNfcIntent(Intent data, byte[] hashToSign, int hashAlgo) { @@ -191,7 +209,7 @@ public class OpenPgpService extends RemoteService { } catch (PassphraseCacheService.KeyNotFoundException e) { // secret key that is set for this account is deleted? // show account config again! - return getCreateAccountIntent(data, data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME)); + return getCreateAccountIntent(data, getAccountName(data)); } } if (passphrase == null) { @@ -270,10 +288,9 @@ public class OpenPgpService extends RemoteService { originalFilename = ""; } - long[] keyIds; - if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) { - keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS); - } else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) { + // first try to get key ids from non-ambiguous key id extra + long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS); + if (keyIds == null) { // get key ids based on given user ids String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); // give params through to activity... @@ -285,20 +302,8 @@ public class OpenPgpService extends RemoteService { // if not success -> result contains a PendingIntent for user interaction return result; } - } else { - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_ERROR, - new OpenPgpError(OpenPgpError.GENERIC_ERROR, - "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] = accSettings.getKeyId(); - // build InputData and write into OutputStream // Get Input- and OutputStream from ParcelFileDescriptor InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); @@ -315,7 +320,8 @@ public class OpenPgpService extends RemoteService { .setCompressionId(accSettings.getCompression()) .setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm()) .setEncryptionMasterKeyIds(keyIds) - .setOriginalFilename(originalFilename); + .setOriginalFilename(originalFilename) + .setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption if (sign) { String passphrase; @@ -334,9 +340,6 @@ public class OpenPgpService extends RemoteService { builder.setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) .setSignatureMasterKeyId(accSettings.getKeyId()) .setSignaturePassphrase(passphrase); - } else { - // encrypt only - builder.setSignatureMasterKeyId(Constants.key.none); } try { @@ -448,7 +451,7 @@ public class OpenPgpService extends RemoteService { // If signature is unknown we return an _additional_ PendingIntent // to retrieve the missing key Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); - intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE); intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId()); intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); @@ -514,7 +517,7 @@ public class OpenPgpService extends RemoteService { // If keys are not in db we return an additional PendingIntent // to retrieve the missing key Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class); - intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE); intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, masterKeyId); intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data); @@ -596,6 +599,16 @@ public class OpenPgpService extends RemoteService { return null; } + private String getAccountName(Intent data) { + String accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME); + // if no account name is given use name "default" + if (TextUtils.isEmpty(accName)) { + accName = "default"; + } + Log.d(Constants.TAG, "accName: " + accName); + return accName; + } + // TODO: multi-threading private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() { @@ -606,12 +619,7 @@ public class OpenPgpService extends RemoteService { return errorResult; } - String accName; - if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) { - accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME); - } else { - accName = "default"; - } + String accName = getAccountName(data); final AccountSettings accSettings = getAccSettings(accName); if (accSettings == null) { return getCreateAccountIntent(data, accName); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java index f70324e2c..e71b52ccd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java @@ -27,6 +27,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; +import android.text.TextUtils; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; @@ -160,7 +161,7 @@ public abstract class RemoteService extends Service { */ protected AccountSettings getAccSettings(String accountName) { String currentPkg = getCurrentCallingPackage(); - Log.d(Constants.TAG, "accountName: " + accountName); + Log.d(Constants.TAG, "getAccSettings accountName: "+ accountName); Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName); @@ -171,7 +172,7 @@ public abstract class RemoteService extends Service { protected Intent getCreateAccountIntent(Intent data, String accountName) { String packageName = getCurrentCallingPackage(); - Log.d(Constants.TAG, "accountName: " + accountName); + Log.d(Constants.TAG, "getCreateAccountIntent accountName: " + accountName); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index 666252353..67ad0822d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -30,6 +30,7 @@ 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.service.OperationResultParcel; import org.sufficientlysecure.keychain.util.Log; public class AccountSettingsActivity extends ActionBarActivity { @@ -106,4 +107,15 @@ public class AccountSettingsActivity extends ActionBarActivity { finish(); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) { + OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT); + result.createNotify(this).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java index 8468f5eca..2cab23e51 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java @@ -36,6 +36,8 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.service.OperationResultParcel; +import org.sufficientlysecure.keychain.service.OperationResults; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment; import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter; @@ -177,24 +179,19 @@ public class AccountSettingsFragment extends Fragment implements switch (requestCode) { case REQUEST_CODE_CREATE_KEY: { if (resultCode == Activity.RESULT_OK) { - // select newly created key - try { - long masterKeyId = new ProviderHelper(getActivity()) - .getCachedPublicKeyRing(data.getData()) - .extractOrGetMasterKeyId(); - mSelectKeyFragment.selectKey(masterKeyId); - } catch (PgpGeneralException e) { - Log.e(Constants.TAG, "key not found!", e); + if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) { + OperationResults.SaveKeyringResult result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT); + mSelectKeyFragment.selectKey(result.mRingMasterKeyId); + } else { + Log.e(Constants.TAG, "missing result!"); } } break; } - - default: - super.onActivityResult(requestCode, resultCode, data); - - break; } + + // execute activity's onActivityResult to show log notify + super.onActivityResult(requestCode, resultCode, data); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java index 48c76d561..4b27e115b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -18,11 +18,20 @@ package org.sufficientlysecure.keychain.remote.ui; import android.content.Intent; +import android.graphics.Color; +import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.ActionBarActivity; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.SpannedString; +import android.text.TextUtils; +import android.text.style.BulletSpan; +import android.text.style.StyleSpan; import android.view.View; import android.widget.TextView; @@ -39,7 +48,6 @@ import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; -import java.security.Provider; import java.util.ArrayList; public class RemoteServiceActivity extends ActionBarActivity { @@ -68,7 +76,8 @@ public class RemoteServiceActivity extends ActionBarActivity { // 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"; - public static final String EXTRA_DUBLICATE_USER_IDS = "dublicate_user_ids"; + public static final String EXTRA_DUPLICATE_USER_IDS = "dublicate_user_ids"; + public static final String EXTRA_NO_USER_IDS_CHECK = "no_user_ids"; // error message public static final String EXTRA_ERROR_MESSAGE = "error_message"; @@ -229,32 +238,41 @@ public class RemoteServiceActivity extends ActionBarActivity { } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); + boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true); ArrayList<String> missingUserIds = intent .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); ArrayList<String> dublicateUserIds = intent - .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS); + .getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS); + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + final SpannableString textIntro = new SpannableString( + noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids) + : getString(R.string.api_select_pub_keys_text) + ); + textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(textIntro); - // TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids - String text = "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>"; - text += "<br/><br/>"; if (missingUserIds != null && missingUserIds.size() > 0) { - text += getString(R.string.api_select_pub_keys_missing_text); - text += "<br/>"; - text += "<ul>"; + ssb.append("\n\n"); + ssb.append(getString(R.string.api_select_pub_keys_missing_text)); + ssb.append("\n"); for (String userId : missingUserIds) { - text += "<li>" + userId + "</li>"; + SpannableString ss = new SpannableString(userId + "\n"); + ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(ss); } - text += "</ul>"; - text += "<br/>"; } if (dublicateUserIds != null && dublicateUserIds.size() > 0) { - text += getString(R.string.api_select_pub_keys_dublicates_text); - text += "<br/>"; - text += "<ul>"; + ssb.append("\n\n"); + ssb.append(getString(R.string.api_select_pub_keys_dublicates_text)); + ssb.append("\n"); for (String userId : dublicateUserIds) { - text += "<li>" + userId + "</li>"; + SpannableString ss = new SpannableString(userId + "\n"); + ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(ss); } - text += "</ul>"; } // Inflate a "Done"/"Cancel" custom action bar view @@ -284,8 +302,8 @@ public class RemoteServiceActivity extends ActionBarActivity { setContentView(R.layout.api_remote_select_pub_keys); // set text on view - HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); - textView.setHtmlFromString(text, true); + TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text); + textView.setText(ssb, TextView.BufferType.SPANNABLE); /* Load select pub keys fragment */ // Check that the activity is using the layout version with diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 43ed2dad0..fbe914b78 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -21,6 +21,8 @@ import android.accounts.Account; import android.app.Service; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.content.SyncResult; import android.os.Bundle; @@ -29,9 +31,11 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.provider.ContactsContract; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeychainApplication; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.EmailKeyHelper; import org.sufficientlysecure.keychain.util.Log; @@ -42,7 +46,7 @@ public class ContactSyncAdapterService extends Service { private class ContactSyncAdapter extends AbstractThreadedSyncAdapter { - private final AtomicBoolean importDone = new AtomicBoolean(false); +// private final AtomicBoolean importDone = new AtomicBoolean(false); public ContactSyncAdapter() { super(ContactSyncAdapterService.this, true); @@ -51,47 +55,59 @@ public class ContactSyncAdapterService extends Service { @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, final SyncResult syncResult) { - importDone.set(false); - KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this); - EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(), - new Handler.Callback() { - @Override - public boolean handleMessage(Message msg) { - Bundle data = msg.getData(); - switch (msg.arg1) { - case KeychainIntentServiceHandler.MESSAGE_OKAY: - Log.d(Constants.TAG, "Syncing... Done."); - synchronized (importDone) { - importDone.set(true); - importDone.notifyAll(); - } - return true; - case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS: - if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) && - data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) { - Log.d(Constants.TAG, "Syncing... Progress: " + - data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" + - data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)); - return false; - } - default: - Log.d(Constants.TAG, "Syncing... " + msg.toString()); - return false; - } - } - }))); - synchronized (importDone) { - try { - if (!importDone.get()) importDone.wait(); - } catch (InterruptedException e) { - Log.w(Constants.TAG, e); - return; - } - } + Log.d(Constants.TAG, "Performing a sync!"); + // TODO: Import is currently disabled for 2.8, until we implement proper origin management +// importDone.set(false); +// KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this); +// EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(), +// new Handler.Callback() { +// @Override +// public boolean handleMessage(Message msg) { +// Bundle data = msg.getData(); +// switch (msg.arg1) { +// case KeychainIntentServiceHandler.MESSAGE_OKAY: +// Log.d(Constants.TAG, "Syncing... Done."); +// synchronized (importDone) { +// importDone.set(true); +// importDone.notifyAll(); +// } +// return true; +// case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS: +// if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) && +// data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) { +// Log.d(Constants.TAG, "Syncing... Progress: " + +// data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" + +// data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)); +// return false; +// } +// default: +// Log.d(Constants.TAG, "Syncing... " + msg.toString()); +// return false; +// } +// } +// }))); +// synchronized (importDone) { +// try { +// if (!importDone.get()) importDone.wait(); +// } catch (InterruptedException e) { +// Log.w(Constants.TAG, e); +// return; +// } +// } ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this); } } + public static void requestSync() { + Bundle extras = new Bundle(); + // no need to wait for internet connection! + extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync( + new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), + ContactsContract.AUTHORITY, + extras); + } + @Override public IBinder onBind(Intent intent) { return new ContactSyncAdapter().getSyncAdapterBinder(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index d6c470e11..021e6bc07 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,11 +50,14 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; +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.service.OperationResults.ConsolidateResult; import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; +import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; import org.sufficientlysecure.keychain.util.FileImportCache; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -102,6 +106,10 @@ public class KeychainIntentService extends IntentService public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING"; + public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE"; + + public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE"; + /* keys for data bundle */ // encrypt, decrypt, import export @@ -139,8 +147,13 @@ public class KeychainIntentService extends IntentService // delete file securely public static final String DELETE_FILE = "deleteFile"; + // delete keyring(s) + public static final String DELETE_KEY_LIST = "delete_list"; + public static final String DELETE_IS_SECRET = "delete_is_secret"; + // import key public static final String IMPORT_KEY_LIST = "import_key_list"; + public static final String IMPORT_KEY_FILE = "import_key_file"; // export key public static final String EXPORT_OUTPUT_STREAM = "export_output_stream"; @@ -162,6 +175,10 @@ public class KeychainIntentService extends IntentService public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id"; public static final String CERTIFY_KEY_UIDS = "sign_key_uids"; + // consolidate + public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; + + /* * possible data keys as result send over messenger */ @@ -176,8 +193,6 @@ public class KeychainIntentService extends IntentService // export public static final String RESULT_EXPORT = "exported"; - public static final String RESULT_IMPORT = "result"; - Messenger mMessenger; private boolean mIsCanceled; @@ -246,27 +261,31 @@ public class KeychainIntentService extends IntentService String originalFilename = getOriginalFilename(data); /* Operation */ - PgpSignEncrypt.Builder builder = - new PgpSignEncrypt.Builder( - new ProviderHelper(this), - inputData, outStream); - builder.setProgressable(this); - - builder.setEnableAsciiArmorOutput(useAsciiArmor) + PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( + new ProviderHelper(this), + inputData, outStream + ); + builder.setProgressable(this) + .setEnableAsciiArmorOutput(useAsciiArmor) .setVersionHeader(PgpHelper.getVersionForHeader(this)) .setCompressionId(compressionId) .setSymmetricEncryptionAlgorithm( Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) .setEncryptionMasterKeyIds(encryptionKeyIds) .setSymmetricPassphrase(symmetricPassphrase) - .setSignatureMasterKeyId(signatureKeyId) - .setEncryptToSigner(true) - .setSignatureHashAlgorithm( - Preferences.getPreferences(this).getDefaultHashAlgorithm()) - .setSignaturePassphrase( - PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)) .setOriginalFilename(originalFilename); + try { + builder.setSignatureMasterKeyId(signatureKeyId) + .setSignaturePassphrase( + PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)) + .setSignatureHashAlgorithm( + Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .setAdditionalEncryptId(signatureKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + // encrypt-only + } + // this assumes that the bytes are cleartext (valid for current implementation!) if (source == IO_BYTES) { builder.setCleartextInput(true); @@ -391,23 +410,41 @@ public class KeychainIntentService extends IntentService } /* Operation */ - ProviderHelper providerHelper = new ProviderHelper(this); PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100)); - EditKeyResult result; + EditKeyResult modifyResult; if (saveParcel.mMasterKeyId != null) { String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE); CanonicalizedSecretKeyRing secRing = - providerHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); + new ProviderHelper(this).getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); - result = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); + modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); } else { - result = keyOperations.createSecretKeyRing(saveParcel); + modifyResult = keyOperations.createSecretKeyRing(saveParcel); } - UncachedKeyRing ring = result.getRing(); + // If the edit operation didn't succeed, exit here + if (!modifyResult.success()) { + // always return SaveKeyringResult, so create one out of the EditKeyResult + SaveKeyringResult saveResult = new SaveKeyringResult( + SaveKeyringResult.RESULT_ERROR, + modifyResult.getLog(), + null); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); + return; + } - providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100)); + UncachedKeyRing ring = modifyResult.getRing(); + + // Save the keyring. The ProviderHelper is initialized with the previous log + SaveKeyringResult saveResult = new ProviderHelper(this, modifyResult.getLog()) + .saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100)); + + // If the edit operation didn't succeed, exit here + if (!saveResult.success()) { + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); + return; + } // cache new passphrase if (saveParcel.mNewPassphrase != null) { @@ -417,8 +454,11 @@ public class KeychainIntentService extends IntentService setProgress(R.string.progress_done, 100, 100); + // make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); + /* Output */ - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); } catch (Exception e) { sendErrorToHandler(e); } @@ -454,17 +494,21 @@ public class KeychainIntentService extends IntentService } else { // get entries from cached file FileImportCache<ParcelableKeyRing> cache = - new FileImportCache<ParcelableKeyRing>(this); + new FileImportCache<ParcelableKeyRing>(this, "key_import.pcl"); entries = cache.readCacheIntoList(); } - PgpImportExport pgpImportExport = new PgpImportExport(this, this); + ProviderHelper providerHelper = new ProviderHelper(this); + PgpImportExport pgpImportExport = new PgpImportExport(this, providerHelper, this); ImportKeyResult result = pgpImportExport.importKeyRings(entries); - Bundle resultData = new Bundle(); - resultData.putParcelable(RESULT_IMPORT, result); + if (result.mSecret > 0) { + providerHelper.consolidateDatabaseStep1(this); + } + // make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); } catch (Exception e) { sendErrorToHandler(e); } @@ -549,8 +593,9 @@ public class KeychainIntentService extends IntentService CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); PgpImportExport pgpImportExport = new PgpImportExport(this, null); - boolean uploaded = pgpImportExport.uploadKeyRingToServer(server, keyring); - if (!uploaded) { + try { + pgpImportExport.uploadKeyRingToServer(server, keyring); + } catch (Keyserver.AddKeyException e) { throw new PgpGeneralException("Unable to export key to selected server"); } @@ -639,7 +684,56 @@ public class KeychainIntentService extends IntentService } catch (Exception e) { sendErrorToHandler(e); } + + } else if (ACTION_DELETE.equals(action)) { + + try { + + long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); + boolean isSecret = data.getBoolean(DELETE_IS_SECRET); + + if (masterKeyIds.length == 0) { + throw new PgpGeneralException("List of keys to delete is empty"); + } + + if (isSecret && masterKeyIds.length > 1) { + throw new PgpGeneralException("Secret keys can only be deleted individually!"); + } + + boolean success = false; + for (long masterKeyId : masterKeyIds) { + int count = getContentResolver().delete( + KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null + ); + success |= count > 0; + } + + if (isSecret && success) { + ConsolidateResult result = + new ProviderHelper(this).consolidateDatabaseStep1(this); + } + + if (success) { + // make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); + + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); + } + + } catch (Exception e) { + sendErrorToHandler(e); + } + + } else if (ACTION_CONSOLIDATE.equals(action)) { + ConsolidateResult result; + if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) { + result = new ProviderHelper(this).consolidateDatabaseStep2(this); + } else { + result = new ProviderHelper(this).consolidateDatabaseStep1(this); + } + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); } + } private void sendErrorToHandler(Exception e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java index d7d98fd68..142bf65cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -155,10 +156,8 @@ public class OperationResultParcel implements Parcelable { if ((resultType & OperationResultParcel.RESULT_ERROR) == 0) { if (getLog().containsWarnings()) { - duration = 0; color = Style.ORANGE; } else { - duration = SuperToast.Duration.LONG; color = Style.GREEN; } @@ -167,7 +166,6 @@ public class OperationResultParcel implements Parcelable { } else { - duration = 0; color = Style.RED; str = "operation failed"; @@ -180,8 +178,8 @@ public class OperationResultParcel implements Parcelable { button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD, Style.getStyle(color, SuperToast.Animations.POPUP)); toast.setText(str); - toast.setDuration(duration); - toast.setIndeterminate(duration == 0); + toast.setDuration(SuperToast.Duration.EXTRA_LONG); + toast.setIndeterminate(false); toast.setSwipeToDismiss(true); // If we have a log and it's non-empty, show a View Log button if (button) { @@ -289,6 +287,7 @@ public class OperationResultParcel implements Parcelable { MSG_IS_SUCCESS (R.string.msg_is_success), // keyring canonicalization + MSG_KC_V3_KEY (R.string.msg_kc_v3_key), MSG_KC_PUBLIC (R.string.msg_kc_public), MSG_KC_SECRET (R.string.msg_kc_secret), MSG_KC_FATAL_NO_UID (R.string.msg_kc_fatal_no_uid), @@ -324,6 +323,7 @@ public class OperationResultParcel implements Parcelable { MSG_KC_UID_BAD_TIME (R.string.msg_kc_uid_bad_time), MSG_KC_UID_BAD_TYPE (R.string.msg_kc_uid_bad_type), MSG_KC_UID_BAD (R.string.msg_kc_uid_bad), + MSG_KC_UID_CERT_DUP (R.string.msg_kc_uid_cert_dup), MSG_KC_UID_DUP (R.string.msg_kc_uid_dup), MSG_KC_UID_FOREIGN (R.string.msg_kc_uid_foreign), MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert), @@ -333,10 +333,11 @@ public class OperationResultParcel implements Parcelable { // keyring consolidation + MSG_MG_ERROR_SECRET_DUMMY(R.string.msg_mg_error_secret_dummy), + MSG_MG_ERROR_ENCODE(R.string.msg_mg_error_encode), + MSG_MG_ERROR_HETEROGENEOUS(R.string.msg_mg_error_heterogeneous), MSG_MG_PUBLIC (R.string.msg_mg_public), MSG_MG_SECRET (R.string.msg_mg_secret), - MSG_MG_FATAL_ENCODE (R.string.msg_mg_fatal_encode), - MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous), MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey), MSG_MG_FOUND_NEW (R.string.msg_mg_found_new), MSG_MG_UNCHANGED (R.string.msg_mg_unchanged), @@ -346,10 +347,16 @@ public class OperationResultParcel implements Parcelable { MSG_CR_ERROR_NO_MASTER (R.string.msg_cr_error_no_master), MSG_CR_ERROR_NO_USER_ID (R.string.msg_cr_error_no_user_id), MSG_CR_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify), + MSG_CR_ERROR_NULL_EXPIRY(R.string.msg_cr_error_null_expiry), MSG_CR_ERROR_KEYSIZE_512 (R.string.msg_cr_error_keysize_512), + MSG_CR_ERROR_NO_KEYSIZE (R.string.msg_cr_error_no_keysize), + MSG_CR_ERROR_NO_CURVE (R.string.msg_cr_error_no_curve), MSG_CR_ERROR_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo), MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp), - MSG_CR_ERROR_MASTER_ELGAMAL (R.string.msg_cr_error_master_elgamal), + MSG_CR_ERROR_FLAGS_DSA (R.string.msg_cr_error_flags_dsa), + MSG_CR_ERROR_FLAGS_ELGAMAL (R.string.msg_cr_error_flags_elgamal), + MSG_CR_ERROR_FLAGS_ECDSA (R.string.msg_cr_error_flags_ecdsa), + MSG_CR_ERROR_FLAGS_ECDH (R.string.msg_cr_error_flags_ecdh), // secret key modify MSG_MF (R.string.msg_mr), @@ -357,18 +364,27 @@ public class OperationResultParcel implements Parcelable { MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint), MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid), MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity), + MSG_MF_ERROR_MASTER_NONE(R.string.msg_mf_error_master_none), + MSG_MF_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify), MSG_MF_ERROR_NOEXIST_PRIMARY (R.string.msg_mf_error_noexist_primary), - MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary), + MSG_MF_ERROR_NOEXIST_REVOKE (R.string.msg_mf_error_noexist_revoke), + MSG_MF_ERROR_NULL_EXPIRY (R.string.msg_mf_error_null_expiry), + MSG_MF_ERROR_PASSPHRASE_MASTER(R.string.msg_mf_error_passphrase_master), + MSG_MF_ERROR_PAST_EXPIRY(R.string.msg_mf_error_past_expiry), MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp), + MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary), MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig), + MSG_MF_ERROR_SUBKEY_MISSING(R.string.msg_mf_error_subkey_missing), + MSG_MF_MASTER (R.string.msg_mf_master), MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase), + MSG_MF_PASSPHRASE_KEY (R.string.msg_mf_passphrase_key), + MSG_MF_PASSPHRASE_EMPTY_RETRY (R.string.msg_mf_passphrase_empty_retry), + MSG_MF_PASSPHRASE_FAIL (R.string.msg_mf_passphrase_fail), MSG_MF_PRIMARY_REPLACE_OLD (R.string.msg_mf_primary_replace_old), MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new), MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change), - MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing), MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id), MSG_MF_SUBKEY_NEW (R.string.msg_mf_subkey_new), - MSG_MF_SUBKEY_PAST_EXPIRY (R.string.msg_mf_subkey_past_expiry), MSG_MF_SUBKEY_REVOKE (R.string.msg_mf_subkey_revoke), MSG_MF_SUCCESS (R.string.msg_mf_success), MSG_MF_UID_ADD (R.string.msg_mf_uid_add), @@ -377,6 +393,32 @@ public class OperationResultParcel implements Parcelable { MSG_MF_UID_ERROR_EMPTY (R.string.msg_mf_uid_error_empty), MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error), MSG_MF_UNLOCK (R.string.msg_mf_unlock), + + // consolidate + MSG_CON_CRITICAL_IN (R.string.msg_con_critical_in), + MSG_CON_CRITICAL_OUT (R.string.msg_con_critical_out), + MSG_CON_DB_CLEAR (R.string.msg_con_db_clear), + MSG_CON_DELETE_PUBLIC (R.string.msg_con_delete_public), + MSG_CON_DELETE_SECRET (R.string.msg_con_delete_secret), + MSG_CON_ERROR_BAD_STATE (R.string.msg_con_error_bad_state), + MSG_CON_ERROR_CONCURRENT(R.string.msg_con_error_concurrent), + MSG_CON_ERROR_DB (R.string.msg_con_error_db), + MSG_CON_ERROR_IO_PUBLIC (R.string.msg_con_error_io_public), + MSG_CON_ERROR_IO_SECRET (R.string.msg_con_error_io_secret), + MSG_CON_ERROR_PUBLIC (R.string.msg_con_error_public), + MSG_CON_ERROR_SECRET (R.string.msg_con_error_secret), + MSG_CON_RECOVER (R.plurals.msg_con_recover), + MSG_CON_RECOVER_UNKNOWN (R.string.msg_con_recover_unknown), + MSG_CON_REIMPORT_PUBLIC (R.plurals.msg_con_reimport_public), + MSG_CON_REIMPORT_PUBLIC_SKIP (R.string.msg_con_reimport_public_skip), + MSG_CON_REIMPORT_SECRET (R.plurals.msg_con_reimport_secret), + MSG_CON_REIMPORT_SECRET_SKIP (R.string.msg_con_reimport_secret_skip), + MSG_CON (R.string.msg_con), + MSG_CON_SAVE_PUBLIC (R.string.msg_con_save_public), + MSG_CON_SAVE_SECRET (R.string.msg_con_save_secret), + MSG_CON_SUCCESS (R.string.msg_con_success), + MSG_CON_WARN_DELETE_PUBLIC (R.string.msg_con_warn_delete_public), + MSG_CON_WARN_DELETE_SECRET (R.string.msg_con_warn_delete_secret), ; private final int mMsgId; @@ -406,7 +448,9 @@ public class OperationResultParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mResult); - dest.writeTypedList(mLog.toList()); + if (mLog != null) { + dest.writeTypedList(mLog.toList()); + } } public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() { @@ -432,6 +476,15 @@ public class OperationResultParcel implements Parcelable { mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null)); } + public boolean containsType(LogType type) { + for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) { + if (entry.mType == type) { + return true; + } + } + return false; + } + public boolean containsWarnings() { for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) { if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java index 543b83edb..f3d0b9e9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,7 +29,10 @@ import com.github.johnpersano.supertoasts.SuperToast; import com.github.johnpersano.supertoasts.util.OnClickWrapper; import com.github.johnpersano.supertoasts.util.Style; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; @@ -37,7 +41,7 @@ public abstract class OperationResults { public static class ImportKeyResult extends OperationResultParcel { - public final int mNewKeys, mUpdatedKeys, mBadKeys; + public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret; // At least one new key public static final int RESULT_OK_NEWKEYS = 2; @@ -49,18 +53,21 @@ public abstract class OperationResults { public static final int RESULT_WITH_WARNINGS = 16; // No keys to import... - public static final int RESULT_FAIL_NOTHING = 32 +1; + public static final int RESULT_FAIL_NOTHING = 32 + 1; public boolean isOkBoth() { return (mResult & (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED)) == (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED); } + public boolean isOkNew() { return (mResult & RESULT_OK_NEWKEYS) == RESULT_OK_NEWKEYS; } + public boolean isOkUpdated() { return (mResult & RESULT_OK_UPDATED) == RESULT_OK_UPDATED; } + public boolean isFailNothing() { return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING; } @@ -70,14 +77,16 @@ public abstract class OperationResults { mNewKeys = source.readInt(); mUpdatedKeys = source.readInt(); mBadKeys = source.readInt(); + mSecret = source.readInt(); } public ImportKeyResult(int result, OperationLog log, - int newKeys, int updatedKeys, int badKeys) { + int newKeys, int updatedKeys, int badKeys, int secret) { super(result, log); mNewKeys = newKeys; mUpdatedKeys = updatedKeys; mBadKeys = badKeys; + mSecret = secret; } @Override @@ -86,6 +95,7 @@ public abstract class OperationResults { dest.writeInt(mNewKeys); dest.writeInt(mUpdatedKeys); dest.writeInt(mBadKeys); + dest.writeInt(mSecret); } public static Creator<ImportKeyResult> CREATOR = new Creator<ImportKeyResult>() { @@ -124,7 +134,7 @@ public abstract class OperationResults { if (this.isOkBoth()) { str = activity.getResources().getQuantityString( R.plurals.import_keys_added_and_updated_1, mNewKeys, mNewKeys); - str += " "+ activity.getResources().getQuantityString( + str += " " + activity.getResources().getQuantityString( R.plurals.import_keys_added_and_updated_2, mUpdatedKeys, mUpdatedKeys, withWarnings); } else if (isOkUpdated()) { str = activity.getResources().getQuantityString( @@ -185,13 +195,13 @@ public abstract class OperationResults { public static class EditKeyResult extends OperationResultParcel { private transient UncachedKeyRing mRing; - public final Long mRingMasterKeyId; + public final long mRingMasterKeyId; public EditKeyResult(int result, OperationLog log, - UncachedKeyRing ring) { + UncachedKeyRing ring) { super(result, log); mRing = ring; - mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : null; + mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; } public UncachedKeyRing getRing() { @@ -224,8 +234,12 @@ public abstract class OperationResults { public static class SaveKeyringResult extends OperationResultParcel { - public SaveKeyringResult(int result, OperationLog log) { + public final long mRingMasterKeyId; + + public SaveKeyringResult(int result, OperationLog log, + CanonicalizedKeyRing ring) { super(result, log); + mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; } // Some old key was updated @@ -240,6 +254,34 @@ public abstract class OperationResults { return (mResult & UPDATED) == UPDATED; } + public SaveKeyringResult(Parcel source) { + super(source); + mRingMasterKeyId = source.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mRingMasterKeyId); + } + + public static Creator<SaveKeyringResult> CREATOR = new Creator<SaveKeyringResult>() { + public SaveKeyringResult createFromParcel(final Parcel source) { + return new SaveKeyringResult(source); + } + + public SaveKeyringResult[] newArray(final int size) { + return new SaveKeyringResult[size]; + } + }; + } + + public static class ConsolidateResult extends OperationResultParcel { + + public ConsolidateResult(int result, OperationLog log) { + super(result, log); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 8cd9876eb..1b357bd65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -78,7 +78,7 @@ public class PassphraseCacheService extends Service { private static final int NOTIFICATION_ID = 1; private static final int MSG_PASSPHRASE_CACHE_GET_OKAY = 1; - private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND = 2; + private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND = 2; private BroadcastReceiver mIntentReceiver; @@ -170,7 +170,7 @@ public class PassphraseCacheService extends Service { switch (returnMessage.what) { case MSG_PASSPHRASE_CACHE_GET_OKAY: return returnMessage.getData().getString(EXTRA_PASSPHRASE); - case MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND: + case MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND: throw new KeyNotFoundException(); default: throw new KeyNotFoundException("should not happen!"); @@ -322,7 +322,7 @@ public class PassphraseCacheService extends Service { msg.setData(bundle); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!"); - msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NO_FOUND; + msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND; } try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 490a8e738..996ce6a5a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,14 +80,16 @@ public class SaveKeyringParcel implements Parcelable { // performance gain for using Parcelable here would probably be negligible, // use Serializable instead. public static class SubkeyAdd implements Serializable { - public int mAlgorithm; - public int mKeysize; + public Algorithm mAlgorithm; + public Integer mKeySize; + public Curve mCurve; public int mFlags; public Long mExpiry; - public SubkeyAdd(int algorithm, int keysize, int flags, Long expiry) { + public SubkeyAdd(Algorithm algorithm, Integer keySize, Curve curve, int flags, Long expiry) { mAlgorithm = algorithm; - mKeysize = keysize; + mKeySize = keySize; + mCurve = curve; mFlags = flags; mExpiry = expiry; } @@ -94,7 +97,8 @@ public class SaveKeyringParcel implements Parcelable { @Override public String toString() { String out = "mAlgorithm: " + mAlgorithm + ", "; - out += "mKeysize: " + mKeysize + ", "; + out += "mKeySize: " + mKeySize + ", "; + out += "mCurve: " + mCurve + ", "; out += "mFlags: " + mFlags; out += "mExpiry: " + mExpiry; @@ -213,4 +217,20 @@ public class SaveKeyringParcel implements Parcelable { return out; } + + // All supported algorithms + public enum Algorithm { + RSA, DSA, ELGAMAL, ECDSA, ECDH + } + + // All curves defined in the standard + // http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269 + public enum Curve { + NIST_P256, NIST_P384, NIST_P521, + + // these are supported by gpg, but they are not in rfc6637 and not supported by BouncyCastle yet + // (adding support would be trivial though -> JcaPGPKeyConverter.java:190) + // BRAINPOOL_P256, BRAINPOOL_P384, BRAINPOOL_P512 + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index c1986825c..8b99d474d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * Copyright (C) 2011 Senecaso * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,6 +41,7 @@ import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageView; import android.widget.ListView; +import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; @@ -53,9 +55,12 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.OperationResultParcel; +import org.sufficientlysecure.keychain.service.OperationResults; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; +import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Notify; @@ -64,18 +69,18 @@ import java.util.ArrayList; /** * Signs the specified public key with the specified secret master key */ -public class CertifyKeyActivity extends ActionBarActivity implements - SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> { +public class CertifyKeyActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor> { private View mCertifyButton; private ImageView mActionCertifyImage; private CheckBox mUploadKeyCheckbox; private Spinner mSelectKeyserverSpinner; + private ScrollView mScrollView; - private SelectSecretKeyLayoutFragment mSelectKeyFragment; + private CertifyKeySpinner mCertifyKeySpinner; private Uri mDataUri; - private long mPubKeyId = 0; - private long mMasterKeyId = 0; + private long mPubKeyId = Constants.key.none; + private long mMasterKeyId = Constants.key.none; private ListView mUserIds; private UserIdsAdapter mUserIdsAdapter; @@ -89,20 +94,24 @@ public class CertifyKeyActivity extends ActionBarActivity implements setContentView(R.layout.certify_key_activity); - mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager() - .findFragmentById(R.id.sign_key_select_key_fragment); + mCertifyKeySpinner = (CertifyKeySpinner) findViewById(R.id.certify_key_spinner); mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox); mCertifyButton = findViewById(R.id.certify_key_certify_button); mActionCertifyImage = (ImageView) findViewById(R.id.certify_key_action_certify_image); mUserIds = (ListView) findViewById(R.id.view_key_user_ids); + mScrollView = (ScrollView) findViewById(R.id.certify_scroll_view); // make certify image gray, like action icons mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), PorterDuff.Mode.SRC_IN); - mSelectKeyFragment.setCallback(this); - mSelectKeyFragment.setFilterCertify(true); + mCertifyKeySpinner.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() { + @Override + public void onKeyChanged(long masterKeyId) { + mMasterKeyId = masterKeyId; + } + }); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) @@ -135,9 +144,9 @@ public class CertifyKeyActivity extends ActionBarActivity implements public void onClick(View v) { if (mPubKeyId != 0) { if (mMasterKeyId == 0) { - mSelectKeyFragment.setError(getString(R.string.select_key_to_certify)); Notify.showNotify(CertifyKeyActivity.this, getString(R.string.select_key_to_certify), Notify.Style.ERROR); + scrollUp(); } else { initiateCertifying(); } @@ -162,6 +171,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); } + private void scrollUp() { + mScrollView.post(new Runnable() { + public void run() { + mScrollView.fullScroll(ScrollView.FOCUS_UP); + } + }); + } + static final String USER_IDS_SELECTION = UserIds.IS_REVOKED + " = 0"; static final String[] KEYRING_PROJECTION = @@ -199,6 +216,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements if (data.moveToFirst()) { // TODO: put findViewById in onCreate! mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID); + mCertifyKeySpinner.setHiddenMasterKeyId(mPubKeyId); String keyIdStr = PgpKeyHelper.convertKeyIdToHex(mPubKeyId); ((TextView) findViewById(R.id.key_id)).setText(keyIdStr); @@ -213,6 +231,9 @@ public class CertifyKeyActivity extends ActionBarActivity implements break; case LOADER_ID_USER_IDS: mUserIdsAdapter.swapCursor(data); + // when some user ids are pre-checked, the focus is requested and the scroll view goes + // down. This fixes it. + scrollUp(); break; } } @@ -292,8 +313,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success, - Notify.Style.INFO); +// Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success, +// Notify.Style.INFO); + + OperationResultParcel result = new OperationResultParcel(OperationResultParcel.RESULT_OK, null); + Intent intent = new Intent(); + intent.putExtra(OperationResultParcel.EXTRA_RESULT, result); + CertifyKeyActivity.this.setResult(RESULT_OK, intent); + CertifyKeyActivity.this.finish(); // check if we need to send the key to the server or not if (mUploadKeyCheckbox.isChecked()) { @@ -345,13 +372,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - Intent intent = new Intent(); - intent.putExtra(OperationResultParcel.EXTRA_RESULT, message.getData()); - Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success, - Notify.Style.INFO); + //Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success, + //Notify.Style.INFO); - setResult(RESULT_OK); - finish(); + OperationResultParcel result = new OperationResultParcel(OperationResultParcel.RESULT_OK, null); + Intent intent = new Intent(); + intent.putExtra(OperationResultParcel.EXTRA_RESULT, result); + CertifyKeyActivity.this.setResult(RESULT_OK, intent); + CertifyKeyActivity.this.finish(); } } }; @@ -367,14 +395,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements startService(intent); } - /** - * callback from select key fragment - */ - @Override - public void onKeySelected(long secretKeyId) { - mMasterKeyId = secretKeyId; - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java new file mode 100644 index 000000000..edc05e28d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.FragmentActivity; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; + +/** + * We can not directly create a dialog on the application context. + * This activity encapsulates a DialogFragment to emulate a dialog. + */ +public class ConsolidateDialogActivity extends FragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // this activity itself has no content view (see manifest) + + consolidateRecovery(); + + } + + private void consolidateRecovery() { + // Message is received after importing is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + this, + getString(R.string.progress_importing), + ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + /* don't care about the results (for now?) + + // get returned data bundle + Bundle returnData = message.getData(); + if (returnData == null) { + return; + } + final ConsolidateResult result = + returnData.getParcelable(KeychainIntentService.RESULT_CONSOLIDATE); + if (result == null) { + return; + } + result.createNotify(ConsolidateDialogActivity.this).show(); + */ + + ConsolidateDialogActivity.this.finish(); + } + } + }; + + // Send all information needed to service to import key in other thread + Intent intent = new Intent(this, KeychainIntentService.class); + intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE); + + // fill values for this action + Bundle data = new Bundle(); + data.putBoolean(KeychainIntentService.CONSOLIDATE_RECOVERY, true); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(this); + + // start service with intent + startService(intent); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 773be816a..69f4af04b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -33,6 +33,7 @@ import android.widget.TextView; import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.sig.KeyFlags; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.KeyRing; @@ -42,6 +43,8 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.service.OperationResults; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Notify; public class CreateKeyFinalFragment extends Fragment { @@ -125,10 +128,9 @@ public class CreateKeyFinalFragment extends Fragment { Intent intent = new Intent(getActivity(), KeychainIntentService.class); intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING); - // Message is received after importing is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( getActivity(), - getString(R.string.progress_importing), + getString(R.string.progress_building_key), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard KeychainIntentServiceHandler first @@ -140,26 +142,21 @@ public class CreateKeyFinalFragment extends Fragment { if (returnData == null) { return; } - final OperationResults.EditKeyResult result = + final OperationResults.SaveKeyringResult result = returnData.getParcelable(OperationResultParcel.EXTRA_RESULT); if (result == null) { + Log.e(Constants.TAG, "result == null"); return; } - if (result.getResult() == OperationResultParcel.RESULT_OK) { - if (mUploadCheckbox.isChecked()) { - // result will be displayed after upload - uploadKey(result); - } else { - // TODO: return result - result.createNotify(getActivity()); - - getActivity().setResult(Activity.RESULT_OK); - getActivity().finish(); - } + if (mUploadCheckbox.isChecked()) { + // result will be displayed after upload + uploadKey(result); } else { - // display result on error without finishing activity - result.createNotify(getActivity()); + Intent data = new Intent(); + data.putExtra(OperationResultParcel.EXTRA_RESULT, result); + getActivity().setResult(Activity.RESULT_OK, data); + getActivity().finish(); } } } @@ -169,9 +166,12 @@ public class CreateKeyFinalFragment extends Fragment { Bundle data = new Bundle(); SaveKeyringParcel parcel = new SaveKeyringParcel(); - parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.CERTIFY_OTHER, null)); - parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.SIGN_DATA, null)); - parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(PublicKeyAlgorithmTags.RSA_GENERAL, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.RSA, 4096, null, KeyFlags.SIGN_DATA, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); String userId = KeyRing.createUserId(mName, mEmail, null); parcel.mAddUserIds.add(userId); parcel.mChangePrimaryUserId = userId; @@ -191,7 +191,7 @@ public class CreateKeyFinalFragment extends Fragment { getActivity().startService(intent); } - private void uploadKey(final OperationResults.EditKeyResult editKeyResult) { + private void uploadKey(final OperationResults.SaveKeyringResult saveKeyResult) { // Send all information needed to service to upload key in other thread final Intent intent = new Intent(getActivity(), KeychainIntentService.class); @@ -199,7 +199,7 @@ public class CreateKeyFinalFragment extends Fragment { // set data uri as path to keyring Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( - editKeyResult.mRingMasterKeyId); + saveKeyResult.mRingMasterKeyId); intent.setData(blobUri); // fill values for this action @@ -211,7 +211,6 @@ public class CreateKeyFinalFragment extends Fragment { intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - // Message is received after uploading is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(), getString(R.string.progress_uploading), ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { @@ -219,20 +218,16 @@ public class CreateKeyFinalFragment extends Fragment { super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // TODO: not supported by upload? -// if (result.getResult() == OperationResultParcel.RESULT_OK) { - // TODO: return result - editKeyResult.createNotify(getActivity()); - - Notify.showNotify(getActivity(), R.string.key_send_success, - Notify.Style.INFO); - - getActivity().setResult(Activity.RESULT_OK); + // TODO: upload operation needs a result! + // TODO: then combine these results + //if (result.getResult() == OperationResultParcel.RESULT_OK) { + //Notify.showNotify(getActivity(), R.string.key_send_success, + //Notify.Style.INFO); + + Intent data = new Intent(); + data.putExtra(OperationResultParcel.EXTRA_RESULT, saveKeyResult); + getActivity().setResult(Activity.RESULT_OK, data); getActivity().finish(); -// } else { -// // display result on error without finishing activity -// editKeyResult.createNotify(getActivity()); -// } } } }; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 03074bb6a..28692f638 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -48,8 +48,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.service.OperationResults; -import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult; +import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; @@ -340,7 +339,8 @@ public class EditKeyFragment extends LoaderFragment implements } else { mSaveKeyringParcel.mRevokeUserIds.add(userId); // not possible to revoke and change to primary user id - if (mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { + if (mSaveKeyringParcel.mChangePrimaryUserId != null + && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { mSaveKeyringParcel.mChangePrimaryUserId = null; } } @@ -407,10 +407,10 @@ public class EditKeyFragment extends LoaderFragment implements @Override public void handleMessage(Message message) { switch (message.what) { - case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE: - Long expiry = (Long) message.getData(). - getSerializable(EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY_DATE); - mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = expiry; + case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY: + mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = + (Long) message.getData().getSerializable( + EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY); break; } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); @@ -504,7 +504,6 @@ public class EditKeyFragment extends LoaderFragment implements private void save(String passphrase) { Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString()); - // Message is received after importing is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( getActivity(), getString(R.string.progress_saving), @@ -520,8 +519,8 @@ public class EditKeyFragment extends LoaderFragment implements if (returnData == null) { return; } - final OperationResults.EditKeyResult result = - returnData.getParcelable(EditKeyResult.EXTRA_RESULT); + final OperationResultParcel result = + returnData.getParcelable(OperationResultParcel.EXTRA_RESULT); if (result == null) { return; } @@ -534,7 +533,7 @@ public class EditKeyFragment extends LoaderFragment implements // if good -> finish, return result to showkey and display there! Intent intent = new Intent(); - intent.putExtra(EditKeyResult.EXTRA_RESULT, result); + intent.putExtra(OperationResultParcel.EXTRA_RESULT, result); getActivity().setResult(EditKeyActivity.RESULT_OK, intent); getActivity().finish(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 7e08f6b7c..4cd694d48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -73,16 +73,13 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryption_key_ids"; // view - ViewPager mViewPagerMode; - //PagerTabStrip mPagerTabStripMode; - PagerTabStripAdapter mTabsAdapterMode; - ViewPager mViewPagerContent; - PagerTabStrip mPagerTabStripContent; - PagerTabStripAdapter mTabsAdapterContent; + private int mCurrentMode = PAGER_MODE_ASYMMETRIC; + private ViewPager mViewPagerContent; + private PagerTabStrip mPagerTabStripContent; + private PagerTabStripAdapter mTabsAdapterContent; // tabs - int mSwitchToMode = PAGER_MODE_ASYMMETRIC; - int mSwitchToContent = PAGER_CONTENT_MESSAGE; + private int mSwitchToContent = PAGER_CONTENT_MESSAGE; private static final int PAGER_MODE_ASYMMETRIC = 0; private static final int PAGER_MODE_SYMMETRIC = 1; @@ -102,7 +99,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn private String mMessage = ""; public boolean isModeSymmetric() { - return PAGER_MODE_SYMMETRIC == mViewPagerMode.getCurrentItem(); + return PAGER_MODE_SYMMETRIC == mCurrentMode; } public boolean isContentMessage() { @@ -312,59 +309,60 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn String title = isContentMessage() ? getString(R.string.title_share_message) : getString(R.string.title_share_file); - // fallback on Android 2.3, otherwise we would get weird results - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - return Intent.createChooser(prototype, title); - } - - // prevent recursion aka Inception :P - String[] blacklist = new String[]{Constants.PACKAGE_NAME + ".ui.EncryptActivity"}; - - List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>(); - - List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0); - List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>(); - if (!resInfoList.isEmpty()) { - for (ResolveInfo resolveInfo : resInfoList) { - // do not add blacklisted ones - if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name)) - continue; - - resInfoListFiltered.add(resolveInfo); - } - - if (!resInfoListFiltered.isEmpty()) { - // sorting for nice readability - Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() { - @Override - public int compare(ResolveInfo first, ResolveInfo second) { - String firstName = first.loadLabel(getPackageManager()).toString(); - String secondName = second.loadLabel(getPackageManager()).toString(); - return firstName.compareToIgnoreCase(secondName); - } - }); - - // create the custom intent list - for (ResolveInfo resolveInfo : resInfoListFiltered) { - Intent targetedShareIntent = (Intent) prototype.clone(); - targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName); - targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); - - LabeledIntent lIntent = new LabeledIntent(targetedShareIntent, - resolveInfo.activityInfo.packageName, - resolveInfo.loadLabel(getPackageManager()), - resolveInfo.activityInfo.icon); - targetedShareIntents.add(lIntent); - } - - // Create chooser with only one Intent in it - Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title); - // append all other Intents - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{})); - return chooserIntent; - } - - } + // Disabled, produced an empty list on Huawei U8860 with Android Version 4.0.3 +// // fallback on Android 2.3, otherwise we would get weird results +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { +// return Intent.createChooser(prototype, title); +// } +// +// // prevent recursion aka Inception :P +// String[] blacklist = new String[]{Constants.PACKAGE_NAME + ".ui.EncryptActivity"}; +// +// List<LabeledIntent> targetedShareIntents = new ArrayList<LabeledIntent>(); +// +// List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(prototype, 0); +// List<ResolveInfo> resInfoListFiltered = new ArrayList<ResolveInfo>(); +// if (!resInfoList.isEmpty()) { +// for (ResolveInfo resolveInfo : resInfoList) { +// // do not add blacklisted ones +// if (resolveInfo.activityInfo == null || Arrays.asList(blacklist).contains(resolveInfo.activityInfo.name)) +// continue; +// +// resInfoListFiltered.add(resolveInfo); +// } +// +// if (!resInfoListFiltered.isEmpty()) { +// // sorting for nice readability +// Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() { +// @Override +// public int compare(ResolveInfo first, ResolveInfo second) { +// String firstName = first.loadLabel(getPackageManager()).toString(); +// String secondName = second.loadLabel(getPackageManager()).toString(); +// return firstName.compareToIgnoreCase(secondName); +// } +// }); +// +// // create the custom intent list +// for (ResolveInfo resolveInfo : resInfoListFiltered) { +// Intent targetedShareIntent = (Intent) prototype.clone(); +// targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName); +// targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); +// +// LabeledIntent lIntent = new LabeledIntent(targetedShareIntent, +// resolveInfo.activityInfo.packageName, +// resolveInfo.loadLabel(getPackageManager()), +// resolveInfo.activityInfo.icon); +// targetedShareIntents.add(lIntent); +// } +// +// // Create chooser with only one Intent in it +// Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title); +// // append all other Intents +// chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{})); +// return chooserIntent; +// } +// +// } // fallback to Android's default chooser return Intent.createChooser(prototype, title); @@ -385,7 +383,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris); } - sendIntent.setType("application/pgp-encrypted"); + sendIntent.setType("application/octet-stream"); } if (!isModeSymmetric() && mEncryptionUserIds != null) { Set<String> users = new HashSet<String>(); @@ -474,13 +472,9 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn } 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); - mTabsAdapterMode = new PagerTabStripAdapter(this); - mViewPagerMode.setAdapter(mTabsAdapterMode); mTabsAdapterContent = new PagerTabStripAdapter(this); mViewPagerContent.setAdapter(mTabsAdapterContent); } @@ -502,10 +496,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn // Handle intent actions handleActions(getIntent()); - - mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class, null, getString(R.string.label_asymmetric)); - mTabsAdapterMode.addTab(EncryptSymmetricFragment.class, null, getString(R.string.label_symmetric)); - mViewPagerMode.setCurrentItem(mSwitchToMode); + updateModeFragment(); mTabsAdapterContent.addTab(EncryptMessageFragment.class, null, getString(R.string.label_message)); mTabsAdapterContent.addTab(EncryptFileFragment.class, null, getString(R.string.label_files)); @@ -521,6 +512,16 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn return super.onCreateOptionsMenu(menu); } + private void updateModeFragment() { + getSupportFragmentManager().beginTransaction() + .replace(R.id.encrypt_pager_mode, + mCurrentMode == PAGER_MODE_SYMMETRIC + ? new EncryptSymmetricFragment() + : new EncryptAsymmetricFragment()) + .commitAllowingStateLoss(); + getSupportFragmentManager().executePendingTransactions(); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.isCheckable()) { @@ -528,9 +529,8 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn } switch (item.getItemId()) { case R.id.check_use_symmetric: - mSwitchToMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC; - - mViewPagerMode.setCurrentItem(mSwitchToMode); + mCurrentMode = item.isChecked() ? PAGER_MODE_SYMMETRIC : PAGER_MODE_ASYMMETRIC; + updateModeFragment(); notifyUpdate(); break; case R.id.check_use_armor: @@ -604,7 +604,7 @@ public class EncryptActivity extends DrawerActivity implements EncryptActivityIn mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); // preselect keys given by intent - mSwitchToMode = PAGER_MODE_ASYMMETRIC; + mCurrentMode = PAGER_MODE_ASYMMETRIC; /** * Main Actions diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index a402b6f68..a508472d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -18,35 +18,22 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Context; -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.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Spinner; -import android.widget.SpinnerAdapter; -import android.widget.TextView; import com.tokenautocomplete.TokenCompleteTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; +import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -54,22 +41,20 @@ import java.util.Iterator; import java.util.List; public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener { - public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id"; - public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; - ProviderHelper mProviderHelper; // view - private Spinner mSign; + private KeySpinner mSign; private EncryptKeyCompletionView mEncryptKeyView; - private SelectSignKeyCursorAdapter mSignAdapter = new SelectSignKeyCursorAdapter(); // model private EncryptActivityInterface mEncryptInterface; @Override public void onNotifyUpdate() { - + if (mSign != null) { + mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey()); + } } @Override @@ -101,17 +86,11 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); - mSign = (Spinner) view.findViewById(R.id.sign); - mSign.setAdapter(mSignAdapter); - mSign.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - setSignatureKeyId(parent.getAdapter().getItemId(position)); - } - + mSign = (KeySpinner) view.findViewById(R.id.sign); + mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() { @Override - public void onNothingSelected(AdapterView<?> parent) { - setSignatureKeyId(Constants.key.none); + public void onKeyChanged(long masterKeyId) { + setSignatureKeyId(masterKeyId); } }); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); @@ -128,42 +107,6 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi // preselect keys given preselectKeys(); - getLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<Cursor>() { - @Override - public Loader<Cursor> 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.buildUnifiedKeyRingsUri(); - - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.KEY_ID, - KeyRings.USER_ID, - KeyRings.IS_EXPIRED, - KeyRings.HAS_SIGN, - KeyRings.HAS_ANY_SECRET - }; - - String where = KeyRings.HAS_ANY_SECRET + " = 1 AND " + KeyRings.HAS_SIGN + " NOT NULL AND " - + KeyRings.IS_REVOKED + " = 0 AND " + KeyRings.IS_EXPIRED + " = 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, where, null, null); - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - mSignAdapter.swapCursor(data); - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - mSignAdapter.swapCursor(null); - } - }); mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() { @Override public void onTokenAdded(Object token) { @@ -194,6 +137,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi KeyRings.buildUnifiedKeyRingUri(signatureKey)); if(keyring.hasAnySecret()) { setSignatureKeyId(keyring.getMasterKeyId()); + mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey()); } } catch (PgpGeneralException e) { Log.e(Constants.TAG, "key not found!", e); @@ -211,6 +155,8 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi Log.e(Constants.TAG, "key not found!", e); } } + // This is to work-around a rendering bug in TokenCompleteTextView + mEncryptKeyView.requestFocus(); updateEncryptionKeys(); } } @@ -233,95 +179,4 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi setEncryptionKeyIds(keyIdsArr); setEncryptionUserIds(userIds.toArray(new String[userIds.size()])); } - - private class SelectSignKeyCursorAdapter extends BaseAdapter implements SpinnerAdapter { - private CursorAdapter inner; - private int mIndexUserId; - private int mIndexKeyId; - private int mIndexMasterKeyId; - - public SelectSignKeyCursorAdapter() { - inner = new CursorAdapter(null, null, 0) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return getActivity().getLayoutInflater().inflate(R.layout.encrypt_asymmetric_signkey, null); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId)); - ((TextView) view.findViewById(android.R.id.title)).setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")")); - ((TextView) view.findViewById(android.R.id.text1)).setText(userId[1]); - ((TextView) view.findViewById(android.R.id.text2)).setText(PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId))); - } - - @Override - public long getItemId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(mIndexMasterKeyId); - } - }; - } - - public Cursor swapCursor(Cursor newCursor) { - if (newCursor == null) return inner.swapCursor(null); - - mIndexKeyId = newCursor.getColumnIndex(KeyRings.KEY_ID); - mIndexUserId = newCursor.getColumnIndex(KeyRings.USER_ID); - mIndexMasterKeyId = newCursor.getColumnIndex(KeyRings.MASTER_KEY_ID); - if (newCursor.moveToFirst()) { - do { - if (newCursor.getLong(mIndexMasterKeyId) == mEncryptInterface.getSignatureKey()) { - mSign.setSelection(newCursor.getPosition() + 1); - } - } while (newCursor.moveToNext()); - } - return inner.swapCursor(newCursor); - } - - @Override - public int getCount() { - return inner.getCount() + 1; - } - - @Override - public Object getItem(int position) { - if (position == 0) return null; - return inner.getItem(position - 1); - } - - @Override - public long getItemId(int position) { - if (position == 0) return Constants.key.none; - return inner.getItemId(position - 1); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v = getDropDownView(position, convertView, parent); - v.findViewById(android.R.id.text1).setVisibility(View.GONE); - return v; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - View v; - if (position == 0) { - if (convertView == null) { - v = inner.newView(null, null, parent); - } else { - v = convertView; - } - ((TextView) v.findViewById(android.R.id.title)).setText("None"); - v.findViewById(android.R.id.text1).setVisibility(View.GONE); - v.findViewById(android.R.id.text2).setVisibility(View.GONE); - } else { - v = inner.getView(position - 1, convertView, parent); - v.findViewById(android.R.id.text1).setVisibility(View.VISIBLE); - v.findViewById(android.R.id.text2).setVisibility(View.VISIBLE); - } - return v; - } - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java index 5f3f170a1..7b608a0a2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java @@ -51,7 +51,7 @@ public class FirstTimeActivity extends ActionBarActivity { mSkipSetup.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - finishSetup(); + finishSetup(null); } }); @@ -80,18 +80,22 @@ public class FirstTimeActivity extends ActionBarActivity { if (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) { if (resultCode == RESULT_OK) { - finishSetup(); + finishSetup(data); } } else { Log.e(Constants.TAG, "No valid request code!"); } } - private void finishSetup() { + private void finishSetup(Intent srcData) { Preferences prefs = Preferences.getPreferences(this); prefs.setFirstTime(false); - Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class); - startActivity(intent); + Intent intent = new Intent(this, KeyListActivity.class); + // give intent through to display notify + if (srcData != null) { + intent.putExtras(srcData); + } + startActivityForResult(intent, 0); finish(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 255290de3..96fa11363 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -45,6 +45,7 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; @@ -64,7 +65,7 @@ public class ImportKeysActivity extends ActionBarActivity { + "IMPORT_KEY_FROM_KEYSERVER"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN_RESULT"; - public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX + public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN"; public static final String ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_FILE_AND_RETURN"; @@ -87,7 +88,7 @@ public class ImportKeysActivity extends ActionBarActivity { 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 + // only used by ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE when used from OpenPgpService public static final String EXTRA_PENDING_INTENT_DATA = "data"; private Intent mPendingIntentData; @@ -170,7 +171,7 @@ public class ImportKeysActivity extends ActionBarActivity { startListFragment(savedInstanceState, importData, null, null); } } else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action) - || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action) + || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(action) || ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(action)) { // only used for OpenPgpService @@ -287,7 +288,9 @@ public class ImportKeysActivity extends ActionBarActivity { @Override public void onPageSelected(int position) { // cancel loader and clear list - mListFragment.destroyLoader(); + if (mListFragment != null) { + mListFragment.destroyLoader(); + } } @Override @@ -456,28 +459,25 @@ public class ImportKeysActivity extends ActionBarActivity { return; } final ImportKeyResult result = - returnData.getParcelable(KeychainIntentService.RESULT_IMPORT); + returnData.getParcelable(OperationResultParcel.EXTRA_RESULT); if (result == null) { + Log.e(Constants.TAG, "result == null"); return; } - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())) { + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction()) + || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { Intent intent = new Intent(); intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); ImportKeysActivity.this.setResult(RESULT_OK, intent); ImportKeysActivity.this.finish(); return; } - if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { + if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) { ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData); ImportKeysActivity.this.finish(); return; } - if (ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { - ImportKeysActivity.this.setResult(RESULT_OK); - ImportKeysActivity.this.finish(); - return; - } result.createNotify(ImportKeysActivity.this).show(); } @@ -503,7 +503,8 @@ public class ImportKeysActivity extends ActionBarActivity { // to prevent Java Binder problems on heavy imports // read FileImportCache for more info. try { - FileImportCache<ParcelableKeyRing> cache = new FileImportCache<ParcelableKeyRing>(this); + FileImportCache<ParcelableKeyRing> cache = + new FileImportCache<ParcelableKeyRing>(this, "key_import.pcl"); cache.writeCache(selectedEntries); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java index d339bc132..88caebc32 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java @@ -29,15 +29,19 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ContactHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.util.Log; +import java.util.List; + public class ImportKeysServerFragment extends Fragment { public static final String ARG_QUERY = "query"; public static final String ARG_KEYSERVER = "keyserver"; @@ -46,7 +50,7 @@ public class ImportKeysServerFragment extends Fragment { private ImportKeysActivity mImportActivity; private View mSearchButton; - private EditText mQueryEditText; + private AutoCompleteTextView mQueryEditText; private View mConfigButton; private View mConfigLayout; private Spinner mServerSpinner; @@ -75,7 +79,7 @@ public class ImportKeysServerFragment extends Fragment { View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false); mSearchButton = view.findViewById(R.id.import_server_search); - mQueryEditText = (EditText) view.findViewById(R.id.import_server_query); + mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.import_server_query); mConfigButton = view.findViewById(R.id.import_server_config_button); mConfigLayout = view.findViewById(R.id.import_server_config); mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner); @@ -93,6 +97,16 @@ public class ImportKeysServerFragment extends Fragment { mSearchButton.setEnabled(false); } + List<String> namesAndEmails = ContactHelper.getContactNames(getActivity()); + namesAndEmails.addAll(ContactHelper.getContactMails(getActivity())); + mQueryEditText.setThreshold(3); + mQueryEditText.setAdapter( + new ArrayAdapter<String> + (getActivity(), android.R.layout.simple_spinner_dropdown_item, + namesAndEmails + ) + ); + mSearchButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 7a6e78a7d..e2ad241e2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,8 +18,11 @@ 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.Menu; import android.view.MenuItem; @@ -28,6 +32,10 @@ import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.OperationResultParcel; +import org.sufficientlysecure.keychain.service.OperationResults.ConsolidateResult; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Notify; @@ -63,6 +71,7 @@ public class KeyListActivity extends DrawerActivity { getMenuInflater().inflate(R.menu.key_list, menu); if (Constants.DEBUG) { + menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true); menu.findItem(R.id.menu_key_list_debug_read).setVisible(true); menu.findItem(R.id.menu_key_list_debug_write).setVisible(true); menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true); @@ -92,6 +101,10 @@ public class KeyListActivity extends DrawerActivity { mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); return true; + case R.id.menu_key_list_debug_cons: + consolidate(); + return true; + case R.id.menu_key_list_debug_read: try { KeychainDatabase.debugBackup(this, true); @@ -133,7 +146,67 @@ public class KeyListActivity extends DrawerActivity { private void createKey() { Intent intent = new Intent(this, CreateKeyActivity.class); - startActivity(intent); + startActivityForResult(intent, 0); + } + + private void consolidate() { + // Message is received after importing is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + this, + getString(R.string.progress_importing), + 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(); + if (returnData == null) { + return; + } + final ConsolidateResult result = + returnData.getParcelable(OperationResultParcel.EXTRA_RESULT); + if (result == null) { + return; + } + + result.createNotify(KeyListActivity.this).show(); + } + } + }; + + // Send all information needed to service to import key in other thread + Intent intent = new Intent(this, KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE); + + // fill values for this action + Bundle data = new Bundle(); + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(this); + + // start service with intent + startService(intent); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) { + OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT); + result.createNotify(this).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 3c97b1128..b4676f9b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,14 +80,15 @@ public class KeyListFragment extends LoaderFragment private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; + // saves the mode object for multiselect, needed for reset at some point + private ActionMode mActionMode = null; + private String mQuery; private SearchView mSearchView; // empty list layout private Button mButtonEmptyCreate; private Button mButtonEmptyImport; - public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012; - /** * Load custom layout with StickyListView from library */ @@ -105,7 +107,7 @@ public class KeyListFragment extends LoaderFragment @Override public void onClick(View v) { Intent intent = new Intent(getActivity(), CreateKeyActivity.class); - startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY); + startActivityForResult(intent, 0); } }); mButtonEmptyImport = (Button) view.findViewById(R.id.key_list_empty_button_import); @@ -115,7 +117,7 @@ public class KeyListFragment extends LoaderFragment public void onClick(View v) { Intent intent = new Intent(getActivity(), ImportKeysActivity.class); intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); - startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY); + startActivityForResult(intent, 0); } }); @@ -148,6 +150,7 @@ public class KeyListFragment extends LoaderFragment public boolean onCreateActionMode(ActionMode mode, Menu menu) { android.view.MenuInflater inflater = getActivity().getMenuInflater(); inflater.inflate(R.menu.key_list_multi, menu); + mActionMode = mode; return true; } @@ -193,6 +196,7 @@ public class KeyListFragment extends LoaderFragment @Override public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; mAdapter.clearSelection(); } @@ -288,6 +292,13 @@ public class KeyListFragment extends LoaderFragment // this view is made visible if no data is available mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty)); + // end action mode, if any + if (mActionMode != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mActionMode.finish(); + } + } + // The list should now be shown. if (isResumed()) { setContentShown(true); @@ -330,12 +341,12 @@ public class KeyListFragment extends LoaderFragment * Show dialog to delete key * * @param masterKeyIds - * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not + * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) { // Can only work on singular secret keys - if(hasSecret && masterKeyIds.length > 1) { + if (hasSecret && masterKeyIds.length > 1) { Notify.showNotify(getActivity(), R.string.secret_cannot_multiple, Notify.Style.ERROR); return; @@ -365,6 +376,7 @@ public class KeyListFragment extends LoaderFragment public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { // Get the searchview MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); + mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem); // Execute this when searching @@ -383,7 +395,6 @@ public class KeyListFragment extends LoaderFragment @Override public boolean onMenuItemActionCollapse(MenuItem item) { mQuery = null; - mSearchView.setQuery("", true); getLoaderManager().restartLoader(0, null, KeyListFragment.this); return true; } @@ -399,11 +410,18 @@ public class KeyListFragment extends LoaderFragment @Override public boolean onQueryTextChange(String s) { + Log.d(Constants.TAG, "onQueryTextChange s:" + s); // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. - mQuery = !TextUtils.isEmpty(s) ? s : null; - getLoaderManager().restartLoader(0, null, this); + // If the nav drawer is opened, onQueryTextChange("") is executed. + // This hack prevents restarting the loader. + // TODO: better way to fix this? + String tmp = (mQuery == null) ? "" : mQuery; + if (!s.equals(tmp)) { + mQuery = s; + getLoaderManager().restartLoader(0, null, this); + } return true; } @@ -479,7 +497,7 @@ public class KeyListFragment extends LoaderFragment boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; boolean isExpired = !cursor.isNull(INDEX_EXPIRY) - && new Date(cursor.getLong(INDEX_EXPIRY)*1000).before(new Date()); + && new Date(cursor.getLong(INDEX_EXPIRY) * 1000).before(new Date()); boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; // Note: order is important! @@ -521,6 +539,7 @@ public class KeyListFragment extends LoaderFragment return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0; } + public long getMasterKeyId(int id) { if (!mCursor.moveToPosition(id)) { throw new IllegalStateException("couldn't move cursor to position " + id); @@ -625,7 +644,7 @@ public class KeyListFragment extends LoaderFragment public boolean isAnySecretSelected() { for (int pos : mSelection.keySet()) { - if(mAdapter.isSecretAvailable(pos)) + if (mAdapter.isSecretAvailable(pos)) return true; } return false; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java index b8386b144..248b8976a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 0e948bf7f..e524c3870 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java index de3236d35..64c1e16be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -317,10 +317,10 @@ public class PreferencesActivity extends PreferenceActivity { private static void initializeHashAlgorithm (final IntegerListPreference mHashAlgorithm, int[] valueIds, String[] entries, String[] values) { - valueIds = new int[]{HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160, + valueIds = new int[]{HashAlgorithmTags.RIPEMD160, HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512,}; - entries = new String[]{"MD5", "RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384", + entries = new String[]{"RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512",}; values = new String[valueIds.length]; for (int i = 0; i < values.length; ++i) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index 341e11d1d..92f38a44c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -166,7 +167,7 @@ public class ViewCertActivity extends ActionBarActivity mStatus.setTextColor(getResources().getColor(R.color.black)); } - String algorithmStr = PgpKeyHelper.getAlgorithmInfo(this, sig.getKeyAlgorithm(), 0); + String algorithmStr = PgpKeyHelper.getAlgorithmInfo(this, sig.getKeyAlgorithm(), null, null); mAlgorithm.setText(algorithmStr); mRowReason.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 28f7b8bf5..2c0881ea4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -54,6 +54,8 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.OperationResultParcel; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout; @@ -303,8 +305,10 @@ public class ViewKeyActivity extends ActionBarActivity implements Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { - setResult(RESULT_CANCELED); - finish(); + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + setResult(RESULT_CANCELED); + finish(); + } } }; @@ -313,6 +317,7 @@ public class ViewKeyActivity extends ActionBarActivity implements @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) { OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT); result.createNotify(this).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java index 5a55b0dad..786537b25 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index 08243f06b..c37cb014e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 1f809cc51..1fed58721 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -155,8 +155,8 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { // don't show full fingerprint on key import holder.fingerprint.setVisibility(View.GONE); - if (entry.getBitStrength() != 0 && entry.getAlgorithm() != null) { - holder.algorithm.setText("" + entry.getBitStrength() + "/" + entry.getAlgorithm()); + if (entry.getAlgorithm() != null) { + holder.algorithm.setText(entry.getAlgorithm()); holder.algorithm.setVisibility(View.VISIBLE); } else { holder.algorithm.setVisibility(View.INVISIBLE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index d457e75bd..489cbcefb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -35,7 +35,9 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import java.util.Calendar; import java.util.Date; +import java.util.TimeZone; public class SubkeysAdapter extends CursorAdapter { private LayoutInflater mInflater; @@ -50,6 +52,7 @@ public class SubkeysAdapter extends CursorAdapter { Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, + Keys.KEY_CURVE_OID, Keys.HAS_SECRET, Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, @@ -64,14 +67,15 @@ public class SubkeysAdapter extends CursorAdapter { private static final int INDEX_RANK = 2; private static final int INDEX_ALGORITHM = 3; private static final int INDEX_KEY_SIZE = 4; - private static final int INDEX_HAS_SECRET = 5; - private static final int INDEX_CAN_CERTIFY = 6; - private static final int INDEX_CAN_ENCRYPT = 7; - private static final int INDEX_CAN_SIGN = 8; - private static final int INDEX_IS_REVOKED = 9; - private static final int INDEX_CREATION = 10; - private static final int INDEX_EXPIRY = 11; - private static final int INDEX_FINGERPRINT = 12; + private static final int INDEX_KEY_CURVE_OID = 5; + private static final int INDEX_HAS_SECRET = 6; + private static final int INDEX_CAN_CERTIFY = 7; + private static final int INDEX_CAN_ENCRYPT = 8; + private static final int INDEX_CAN_SIGN = 9; + private static final int INDEX_IS_REVOKED = 10; + private static final int INDEX_CREATION = 11; + private static final int INDEX_EXPIRY = 12; + private static final int INDEX_FINGERPRINT = 13; public SubkeysAdapter(Context context, Cursor c, int flags, SaveKeyringParcel saveKeyringParcel) { @@ -139,7 +143,8 @@ public class SubkeysAdapter extends CursorAdapter { String algorithmStr = PgpKeyHelper.getAlgorithmInfo( context, cursor.getInt(INDEX_ALGORITHM), - cursor.getInt(INDEX_KEY_SIZE) + cursor.getInt(INDEX_KEY_SIZE), + cursor.getString(INDEX_KEY_CURVE_OID) ); vKeyId.setText(keyIdStr); @@ -183,7 +188,7 @@ public class SubkeysAdapter extends CursorAdapter { SaveKeyringParcel.SubkeyChange subkeyChange = mSaveKeyringParcel.getSubkeyChange(keyId); if (subkeyChange != null) { - if (subkeyChange.mExpiry == null) { + if (subkeyChange.mExpiry == null || subkeyChange.mExpiry == 0L) { expiryDate = null; } else { expiryDate = new Date(subkeyChange.mExpiry * 1000); @@ -198,9 +203,13 @@ public class SubkeysAdapter extends CursorAdapter { boolean isExpired; if (expiryDate != null) { isExpired = expiryDate.before(new Date()); + Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expiryCal.setTime(expiryDate); + // convert from UTC to time zone of device + expiryCal.setTimeZone(TimeZone.getDefault()); vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": " - + DateFormat.getDateFormat(context).format(expiryDate)); + + DateFormat.getDateFormat(context).format(expiryCal.getTime())); } else { isExpired = false; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java index be2e17c63..d50318fb4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAddedAdapter.java @@ -33,21 +33,19 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.TimeZone; public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAdd> { private LayoutInflater mInflater; private Activity mActivity; - // hold a private reference to the underlying data List - private List<SaveKeyringParcel.SubkeyAdd> mData; - public SubkeysAddedAdapter(Activity activity, List<SaveKeyringParcel.SubkeyAdd> data) { super(activity, -1, data); mActivity = activity; mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mData = data; } static class ViewHolder { @@ -101,16 +99,21 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd String algorithmStr = PgpKeyHelper.getAlgorithmInfo( mActivity, holder.mModel.mAlgorithm, - holder.mModel.mKeysize + holder.mModel.mKeySize, + holder.mModel.mCurve ); holder.vKeyId.setText(R.string.edit_key_new_subkey); holder.vKeyDetails.setText(algorithmStr); - if (holder.mModel.mExpiry != null) { + if (holder.mModel.mExpiry != 0L) { Date expiryDate = new Date(holder.mModel.mExpiry * 1000); + Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expiryCal.setTime(expiryDate); + // convert from UTC to time zone of device + expiryCal.setTimeZone(TimeZone.getDefault()); holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": " - + DateFormat.getDateFormat(getContext()).format(expiryDate)); + + DateFormat.getDateFormat(getContext()).format(expiryCal.getTime())); } else { holder.vKeyExpiry.setText(getContext().getString(R.string.label_expiry) + ": " + getContext().getString(R.string.none)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index cb31978e9..b4119a5eb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -20,19 +20,18 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.Dialog; -import android.content.DialogInterface; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.text.Editable; import android.text.TextWatcher; -import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.DatePicker; @@ -40,17 +39,18 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TableRow; import android.widget.TextView; +import android.widget.Toast; -import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Choice; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Date; import java.util.TimeZone; public class AddSubkeyDialogFragment extends DialogFragment { @@ -67,7 +67,10 @@ public class AddSubkeyDialogFragment extends DialogFragment { private TableRow mExpiryRow; private DatePicker mExpiryDatePicker; private Spinner mAlgorithmSpinner; + private View mKeySizeRow; private Spinner mKeySizeSpinner; + private View mCurveRow; + private Spinner mCurveSpinner; private TextView mCustomKeyTextView; private EditText mCustomKeyEditText; private TextView mCustomKeyInfoTextView; @@ -76,6 +79,8 @@ public class AddSubkeyDialogFragment extends DialogFragment { private CheckBox mFlagEncrypt; private CheckBox mFlagAuthenticate; + private boolean mWillBeMasterKey; + public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) { mAlgorithmSelectedListener = listener; } @@ -96,7 +101,7 @@ public class AddSubkeyDialogFragment extends DialogFragment { final FragmentActivity context = getActivity(); final LayoutInflater mInflater; - final boolean willBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY); + mWillBeMasterKey = getArguments().getBoolean(ARG_WILL_BE_MASTER_KEY); mInflater = context.getLayoutInflater(); CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context); @@ -110,6 +115,9 @@ public class AddSubkeyDialogFragment extends DialogFragment { mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker); mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm); mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size); + mCurveSpinner = (Spinner) view.findViewById(R.id.add_subkey_curve); + mKeySizeRow = view.findViewById(R.id.add_subkey_row_size); + mCurveRow = view.findViewById(R.id.add_subkey_row_curve); mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label); mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input); mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info); @@ -130,27 +138,36 @@ public class AddSubkeyDialogFragment extends DialogFragment { }); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - mExpiryDatePicker.setMinDate(new Date().getTime() + DateUtils.DAY_IN_MILLIS); + // date picker works based on default time zone + Calendar minDateCal = Calendar.getInstance(TimeZone.getDefault()); + minDateCal.add(Calendar.DAY_OF_YEAR, 1); // at least one day after creation (today) + mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime()); } - ArrayList<Choice> choices = new ArrayList<Choice>(); - choices.add(new Choice(PublicKeyAlgorithmTags.DSA, getResources().getString( - R.string.dsa))); - if (!willBeMasterKey) { - choices.add(new Choice(PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, getResources().getString( - R.string.elgamal))); - } - choices.add(new Choice(PublicKeyAlgorithmTags.RSA_GENERAL, getResources().getString( - R.string.rsa))); - ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(context, - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mAlgorithmSpinner.setAdapter(adapter); - // make RSA the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == PublicKeyAlgorithmTags.RSA_GENERAL) { - mAlgorithmSpinner.setSelection(i); - break; + { + ArrayList<Choice<Algorithm>> choices = new ArrayList<Choice<Algorithm>>(); + choices.add(new Choice<Algorithm>(Algorithm.DSA, getResources().getString( + R.string.dsa))); + if (!mWillBeMasterKey) { + choices.add(new Choice<Algorithm>(Algorithm.ELGAMAL, getResources().getString( + R.string.elgamal))); + } + choices.add(new Choice<Algorithm>(Algorithm.RSA, getResources().getString( + R.string.rsa))); + choices.add(new Choice<Algorithm>(Algorithm.ECDSA, getResources().getString( + R.string.ecdsa))); + choices.add(new Choice<Algorithm>(Algorithm.ECDH, getResources().getString( + R.string.ecdh))); + ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<Choice<Algorithm>>(context, + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mAlgorithmSpinner.setAdapter(adapter); + // make RSA the default + for (int i = 0; i < choices.size(); ++i) { + if (choices.get(i).getId() == Algorithm.RSA) { + mAlgorithmSpinner.setSelection(i); + break; + } } } @@ -161,58 +178,42 @@ public class AddSubkeyDialogFragment extends DialogFragment { mKeySizeSpinner.setAdapter(keySizeAdapter); mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length - - dialog.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface di, int id) { - di.dismiss(); - Choice newKeyAlgorithmChoice = (Choice) mAlgorithmSpinner.getSelectedItem(); - int newKeySize = getProperKeyLength(newKeyAlgorithmChoice.getId(), getSelectedKeyLength()); - - int flags = 0; - if (mFlagCertify.isChecked()) { - flags |= KeyFlags.CERTIFY_OTHER; - } - if (mFlagSign.isChecked()) { - flags |= KeyFlags.SIGN_DATA; - } - if (mFlagEncrypt.isChecked()) { - flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; - } - if (mFlagAuthenticate.isChecked()) { - flags |= KeyFlags.AUTHENTICATION; - } - - Long expiry; - if (mNoExpiryCheckBox.isChecked()) { - expiry = null; - } else { - Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - //noinspection ResourceType - selectedCal.set(mExpiryDatePicker.getYear(), - mExpiryDatePicker.getMonth(), mExpiryDatePicker.getDayOfMonth()); - expiry = selectedCal.getTime().getTime() / 1000; - } - - SaveKeyringParcel.SubkeyAdd newSubkey = new SaveKeyringParcel.SubkeyAdd( - newKeyAlgorithmChoice.getId(), - newKeySize, - flags, - expiry - ); - mAlgorithmSelectedListener.onAlgorithmSelected(newSubkey); - } + { + ArrayList<Choice<Curve>> choices = new ArrayList<Choice<Curve>>(); + + choices.add(new Choice<Curve>(Curve.NIST_P256, getResources().getString( + R.string.key_curve_nist_p256))); + choices.add(new Choice<Curve>(Curve.NIST_P384, getResources().getString( + R.string.key_curve_nist_p384))); + choices.add(new Choice<Curve>(Curve.NIST_P521, getResources().getString( + R.string.key_curve_nist_p521))); + + /* @see SaveKeyringParcel + choices.add(new Choice<Curve>(Curve.BRAINPOOL_P256, getResources().getString( + R.string.key_curve_bp_p256))); + choices.add(new Choice<Curve>(Curve.BRAINPOOL_P384, getResources().getString( + R.string.key_curve_bp_p384))); + choices.add(new Choice<Curve>(Curve.BRAINPOOL_P512, getResources().getString( + R.string.key_curve_bp_p512))); + */ + + ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<Choice<Curve>>(context, + android.R.layout.simple_spinner_item, choices); + mCurveSpinner.setAdapter(adapter); + // make NIST P-256 the default + for (int i = 0; i < choices.size(); ++i) { + if (choices.get(i).getId() == Curve.NIST_P256) { + mCurveSpinner.setSelection(i); + break; } - ); + } + } dialog.setCancelable(true); - dialog.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface di, int id) { - di.dismiss(); - } - } - ); + + // onClickListener are set in onStart() to override default dismiss behaviour + dialog.setPositiveButton(android.R.string.ok, null); + dialog.setNegativeButton(android.R.string.cancel, null); final AlertDialog alertDialog = dialog.show(); @@ -246,7 +247,7 @@ public class AddSubkeyDialogFragment extends DialogFragment { mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - setKeyLengthSpinnerValuesForAlgorithm(((Choice) parent.getSelectedItem()).getId()); + updateUiForAlgorithm(((Choice<Algorithm>) parent.getSelectedItem()).getId()); setCustomKeyVisibility(); setOkButtonAvailability(alertDialog); @@ -260,6 +261,79 @@ public class AddSubkeyDialogFragment extends DialogFragment { return alertDialog; } + @Override + public void onStart() { + super.onStart(); //super.onStart() is where dialog.show() is actually called on the underlying dialog, so we have to do it after this point + AlertDialog d = (AlertDialog) getDialog(); + if (d != null) { + Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE); + Button negativeButton = d.getButton(Dialog.BUTTON_NEGATIVE); + positiveButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!mFlagCertify.isChecked() && !mFlagSign.isChecked() + && !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) { + Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show(); + return; + } + + Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId(); + Curve curve = null; + Integer keySize = null; + // For EC keys, add a curve + if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) { + curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId(); + // Otherwise, get a keysize + } else { + keySize = getProperKeyLength(algorithm, getSelectedKeyLength()); + } + + int flags = 0; + if (mFlagCertify.isChecked()) { + flags |= KeyFlags.CERTIFY_OTHER; + } + if (mFlagSign.isChecked()) { + flags |= KeyFlags.SIGN_DATA; + } + if (mFlagEncrypt.isChecked()) { + flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } + if (mFlagAuthenticate.isChecked()) { + flags |= KeyFlags.AUTHENTICATION; + } + + long expiry; + if (mNoExpiryCheckBox.isChecked()) { + expiry = 0L; + } else { + Calendar selectedCal = Calendar.getInstance(TimeZone.getDefault()); + //noinspection ResourceType + selectedCal.set(mExpiryDatePicker.getYear(), + mExpiryDatePicker.getMonth(), mExpiryDatePicker.getDayOfMonth()); + // date picker uses default time zone, we need to convert to UTC + selectedCal.setTimeZone(TimeZone.getTimeZone("UTC")); + + expiry = selectedCal.getTime().getTime() / 1000; + } + + SaveKeyringParcel.SubkeyAdd newSubkey = new SaveKeyringParcel.SubkeyAdd( + algorithm, keySize, curve, flags, expiry + ); + mAlgorithmSelectedListener.onAlgorithmSelected(newSubkey); + + // finally, dismiss the dialogue + dismiss(); + } + }); + negativeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + } + } + private int getSelectedKeyLength() { final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); final String customLengthString = getResources().getString(R.string.key_size_custom); @@ -290,16 +364,16 @@ public class AddSubkeyDialogFragment extends DialogFragment { * @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, if key length is * inappropriate. */ - private int getProperKeyLength(int algorithmId, int currentKeyLength) { + private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) { final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192}; int properKeyLength = -1; - switch (algorithmId) { - case PublicKeyAlgorithmTags.RSA_GENERAL: + switch (algorithm) { + case RSA: if (currentKeyLength > 1024 && currentKeyLength <= 16384) { properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8); } break; - case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case ELGAMAL: int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length]; for (int i = 0; i < elGamalSupportedLengths.length; i++) { elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength); @@ -314,7 +388,7 @@ public class AddSubkeyDialogFragment extends DialogFragment { } properKeyLength = elGamalSupportedLengths[minimalIndex]; break; - case PublicKeyAlgorithmTags.DSA: + case DSA: if (currentKeyLength >= 512 && currentKeyLength <= 1024) { properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64); } @@ -324,10 +398,10 @@ public class AddSubkeyDialogFragment extends DialogFragment { } private void setOkButtonAvailability(AlertDialog alertDialog) { - final Choice selectedAlgorithm = (Choice) mAlgorithmSpinner.getSelectedItem(); - final int selectedKeySize = getSelectedKeyLength(); //Integer.parseInt((String) mKeySizeSpinner.getSelectedItem()); - final int properKeyLength = getProperKeyLength(selectedAlgorithm.getId(), selectedKeySize); - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(properKeyLength > 0); + Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId(); + boolean enabled = algorithm == Algorithm.ECDSA || algorithm == Algorithm.ECDH + || getProperKeyLength(algorithm, getSelectedKeyLength()) > 0; + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled); } private void setCustomKeyVisibility() { @@ -343,38 +417,104 @@ public class AddSubkeyDialogFragment extends DialogFragment { // hide keyboard after setting visibility to gone if (visibility == View.GONE) { InputMethodManager imm = (InputMethodManager) - getActivity().getSystemService(getActivity().INPUT_METHOD_SERVICE); + getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0); } } - private void setKeyLengthSpinnerValuesForAlgorithm(int algorithmId) { + private void updateUiForAlgorithm(Algorithm algorithm) { final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter(); - final Object selectedItem = mKeySizeSpinner.getSelectedItem(); keySizeAdapter.clear(); - switch (algorithmId) { - case PublicKeyAlgorithmTags.RSA_GENERAL: + switch (algorithm) { + case RSA: replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values); + mKeySizeSpinner.setSelection(1); + mKeySizeRow.setVisibility(View.VISIBLE); + mCurveRow.setVisibility(View.GONE); mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa)); + // allowed flags: + mFlagSign.setEnabled(true); + mFlagEncrypt.setEnabled(true); + mFlagAuthenticate.setEnabled(true); + + if (mWillBeMasterKey) { + mFlagCertify.setEnabled(true); + + mFlagCertify.setChecked(true); + mFlagSign.setChecked(false); + mFlagEncrypt.setChecked(false); + } else { + mFlagCertify.setEnabled(false); + + mFlagCertify.setChecked(false); + mFlagSign.setChecked(true); + mFlagEncrypt.setChecked(true); + } + mFlagAuthenticate.setChecked(false); break; - case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case ELGAMAL: replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values); + mKeySizeSpinner.setSelection(3); + mKeySizeRow.setVisibility(View.VISIBLE); + mCurveRow.setVisibility(View.GONE); mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length + // allowed flags: + mFlagCertify.setChecked(false); + mFlagCertify.setEnabled(false); + mFlagSign.setChecked(false); + mFlagSign.setEnabled(false); + mFlagEncrypt.setChecked(true); + mFlagEncrypt.setEnabled(true); + mFlagAuthenticate.setChecked(false); + mFlagAuthenticate.setEnabled(false); break; - case PublicKeyAlgorithmTags.DSA: + case DSA: replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values); + mKeySizeSpinner.setSelection(2); + mKeySizeRow.setVisibility(View.VISIBLE); + mCurveRow.setVisibility(View.GONE); mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa)); + // allowed flags: + mFlagCertify.setChecked(false); + mFlagCertify.setEnabled(false); + mFlagSign.setChecked(true); + mFlagSign.setEnabled(true); + mFlagEncrypt.setChecked(false); + mFlagEncrypt.setEnabled(false); + mFlagAuthenticate.setChecked(false); + mFlagAuthenticate.setEnabled(false); + break; + case ECDSA: + mKeySizeRow.setVisibility(View.GONE); + mCurveRow.setVisibility(View.VISIBLE); + mCustomKeyInfoTextView.setText(""); + // allowed flags: + mFlagCertify.setEnabled(mWillBeMasterKey); + mFlagCertify.setChecked(mWillBeMasterKey); + mFlagSign.setEnabled(true); + mFlagSign.setChecked(!mWillBeMasterKey); + mFlagEncrypt.setEnabled(false); + mFlagEncrypt.setChecked(false); + mFlagAuthenticate.setEnabled(true); + mFlagAuthenticate.setChecked(false); + break; + case ECDH: + mKeySizeRow.setVisibility(View.GONE); + mCurveRow.setVisibility(View.VISIBLE); + mCustomKeyInfoTextView.setText(""); + // allowed flags: + mFlagCertify.setChecked(false); + mFlagCertify.setEnabled(false); + mFlagSign.setChecked(false); + mFlagSign.setEnabled(false); + mFlagEncrypt.setChecked(true); + mFlagEncrypt.setEnabled(true); + mFlagAuthenticate.setChecked(false); + mFlagAuthenticate.setEnabled(false); break; } keySizeAdapter.notifyDataSetChanged(); - // when switching algorithm, try to select same key length as before - for (int i = 0; i < keySizeAdapter.getCount(); i++) { - if (selectedItem.equals(keySizeAdapter.getItem(i))) { - mKeySizeSpinner.setSelection(i); - break; - } - } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 4927a4d24..d0c9cea5b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -18,7 +18,9 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Dialog; +import android.app.ProgressDialog; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; import android.os.Message; import android.os.Messenger; @@ -31,9 +33,10 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.util.Log; import java.util.HashMap; @@ -48,8 +51,6 @@ public class DeleteKeyDialogFragment extends DialogFragment { private TextView mMainMessage; private View mInflateView; - private Messenger mMessenger; - /** * Creates new instance of this delete file dialog fragment */ @@ -68,7 +69,7 @@ public class DeleteKeyDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); - mMessenger = getArguments().getParcelable(ARG_MESSENGER); + final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER); final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); @@ -83,6 +84,8 @@ public class DeleteKeyDialogFragment extends DialogFragment { builder.setTitle(R.string.warning); + final boolean hasSecret; + // If only a single key has been selected if (masterKeyIds.length == 1) { long masterKeyId = masterKeyIds[0]; @@ -98,7 +101,7 @@ public class DeleteKeyDialogFragment extends DialogFragment { } ); String userId = (String) data.get(KeyRings.USER_ID); - boolean hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1; + hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1; // Set message depending on which key it is. mMainMessage.setText(getString( @@ -107,12 +110,12 @@ public class DeleteKeyDialogFragment extends DialogFragment { userId )); } catch (ProviderHelper.NotFoundException e) { - sendMessageToHandler(MESSAGE_ERROR, null); dismiss(); return null; } } else { mMainMessage.setText(R.string.key_deletion_confirmation_multi); + hasSecret = false; } builder.setIcon(R.drawable.ic_dialog_alert_holo_light); @@ -120,18 +123,45 @@ public class DeleteKeyDialogFragment extends DialogFragment { @Override public void onClick(DialogInterface dialog, int which) { - boolean success = false; - for (long masterKeyId : masterKeyIds) { - int count = activity.getContentResolver().delete( - KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null - ); - success = count > 0; - } - if (success) { - sendMessageToHandler(MESSAGE_OKAY, null); - } else { - sendMessageToHandler(MESSAGE_ERROR, null); - } + // Send all information needed to service to import key in other thread + Intent intent = new Intent(getActivity(), KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_DELETE); + + // Message is received after importing is done in KeychainIntentService + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( + getActivity(), + getString(R.string.progress_deleting), + ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard KeychainIntentServiceHandler first + super.handleMessage(message); + try { + Message msg = Message.obtain(); + msg.copyFrom(message); + messenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "messenger error", e); + } + } + }; + + // fill values for this action + Bundle data = new Bundle(); + data.putLongArray(KeychainIntentService.DELETE_KEY_LIST, masterKeyIds); + data.putBoolean(KeychainIntentService.DELETE_IS_SECRET, hasSecret); + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(saveHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + saveHandler.showProgressDialog(getActivity()); + + // start service with intent + getActivity().startService(intent); + dismiss(); } }); @@ -146,23 +176,4 @@ public class DeleteKeyDialogFragment extends DialogFragment { return builder.show(); } - /** - * 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/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java index aa63f9944..f46e253c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java @@ -25,9 +25,10 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; -import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.DatePicker; import org.sufficientlysecure.keychain.Constants; @@ -40,17 +41,14 @@ import java.util.TimeZone; public class EditSubkeyExpiryDialogFragment extends DialogFragment { private static final String ARG_MESSENGER = "messenger"; - private static final String ARG_CREATION_DATE = "creation_date"; - private static final String ARG_EXPIRY_DATE = "expiry_date"; + private static final String ARG_CREATION = "creation"; + private static final String ARG_EXPIRY = "expiry"; - public static final int MESSAGE_NEW_EXPIRY_DATE = 1; + public static final int MESSAGE_NEW_EXPIRY = 1; public static final int MESSAGE_CANCEL = 2; - public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date"; + public static final String MESSAGE_DATA_EXPIRY = "expiry"; private Messenger mMessenger; - private Calendar mExpiryCal; - - private DatePicker mDatePicker; /** * Creates new instance of this dialog fragment @@ -60,8 +58,8 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { EditSubkeyExpiryDialogFragment frag = new EditSubkeyExpiryDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); - args.putSerializable(ARG_CREATION_DATE, creationDate); - args.putSerializable(ARG_EXPIRY_DATE, expiryDate); + args.putSerializable(ARG_CREATION, creationDate); + args.putSerializable(ARG_EXPIRY, expiryDate); frag.setArguments(args); @@ -75,15 +73,17 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); mMessenger = getArguments().getParcelable(ARG_MESSENGER); - Date creationDate = new Date(getArguments().getLong(ARG_CREATION_DATE) * 1000); - Date expiryDate = new Date(getArguments().getLong(ARG_EXPIRY_DATE) * 1000); + long creation = getArguments().getLong(ARG_CREATION); + long expiry = getArguments().getLong(ARG_EXPIRY); Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(creationDate); - mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - mExpiryCal.setTime(expiryDate); + creationCal.setTime(new Date(creation * 1000)); + final Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expiryCal.setTime(new Date(expiry * 1000)); - Log.d(Constants.TAG, "onCreateDialog"); + // date picker works with default time zone, we need to convert from UTC to default timezone + creationCal.setTimeZone(TimeZone.getDefault()); + expiryCal.setTimeZone(TimeZone.getDefault()); // Explicitly not using DatePickerDialog here! // DatePickerDialog is difficult to customize and has many problems (see old git versions) @@ -95,19 +95,64 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { View view = inflater.inflate(R.layout.edit_subkey_expiry_dialog, null); alert.setView(view); - mDatePicker = (DatePicker) view.findViewById(R.id.edit_subkey_expiry_date_picker); + final CheckBox noExpiry = (CheckBox) view.findViewById(R.id.edit_subkey_expiry_no_expiry); + final DatePicker datePicker = (DatePicker) view.findViewById(R.id.edit_subkey_expiry_date_picker); + + noExpiry.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + datePicker.setVisibility(View.GONE); + } else { + datePicker.setVisibility(View.VISIBLE); + } + } + }); + + // init date picker with default selected date + if (expiry == 0L) { + noExpiry.setChecked(true); + datePicker.setVisibility(View.GONE); + + Calendar todayCal = Calendar.getInstance(TimeZone.getDefault()); + if (creationCal.after(todayCal)) { + // Note: This is just for the rare cases where creation is _after_ today + + // set it to creation date +1 day (don't set it to creationCal, it would break crash + // datePicker.setMinDate() execution with IllegalArgumentException + Calendar creationCalPlusOne = (Calendar) creationCal.clone(); + creationCalPlusOne.add(Calendar.DAY_OF_YEAR, 1); + datePicker.init( + creationCalPlusOne.get(Calendar.YEAR), + creationCalPlusOne.get(Calendar.MONTH), + creationCalPlusOne.get(Calendar.DAY_OF_MONTH), + null + ); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - // will crash with IllegalArgumentException if we set a min date - // that is not before expiry - if (creationCal.before(mExpiryCal)) { - mDatePicker.setMinDate(creationCal.getTime().getTime() - + DateUtils.DAY_IN_MILLIS); } else { - // when creation date isn't available - mDatePicker.setMinDate(mExpiryCal.getTime().getTime() - + DateUtils.DAY_IN_MILLIS); + // normally, just init with today + datePicker.init( + todayCal.get(Calendar.YEAR), + todayCal.get(Calendar.MONTH), + todayCal.get(Calendar.DAY_OF_MONTH), + null + ); } + } else { + noExpiry.setChecked(false); + datePicker.setVisibility(View.VISIBLE); + + // set date picker to current expiry + datePicker.init( + expiryCal.get(Calendar.YEAR), + expiryCal.get(Calendar.MONTH), + expiryCal.get(Calendar.DAY_OF_MONTH), + null + ); + } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + datePicker.setMinDate(creationCal.getTime().getTime()); } alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @@ -115,34 +160,28 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); - Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - //noinspection ResourceType - selectedCal.set(mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); + long expiry; + if (noExpiry.isChecked()) { + expiry = 0L; + } else { + Calendar selectedCal = Calendar.getInstance(TimeZone.getDefault()); + //noinspection ResourceType + selectedCal.set(datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth()); + // date picker uses default time zone, we need to convert to UTC + selectedCal.setTimeZone(TimeZone.getTimeZone("UTC")); - if (mExpiryCal != null) { long numDays = (selectedCal.getTimeInMillis() / 86400000) - - (mExpiryCal.getTimeInMillis() / 86400000); - if (numDays > 0) { - Bundle data = new Bundle(); - data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000); - sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); + - (expiryCal.getTimeInMillis() / 86400000); + if (numDays <= 0) { + Log.e(Constants.TAG, "Should not happen! Expiry num of days <= 0!"); + throw new RuntimeException(); } - } else { - Bundle data = new Bundle(); - data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime().getTime() / 1000); - sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); + expiry = selectedCal.getTime().getTime() / 1000; } - } - }); - - alert.setNeutralButton(R.string.btn_no_date, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dismiss(); Bundle data = new Bundle(); - data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null); - sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data); + data.putSerializable(MESSAGE_DATA_EXPIRY, expiry); + sendMessageToHandler(MESSAGE_NEW_EXPIRY, data); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java index 093a04aff..0324dd3b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java @@ -25,6 +25,7 @@ import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnKeyListener; import android.os.Bundle; import android.support.v4.app.DialogFragment; +import android.view.ContextThemeWrapper; import android.view.KeyEvent; import org.sufficientlysecure.keychain.R; @@ -113,7 +114,12 @@ public class ProgressDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); - ProgressDialog dialog = new ProgressDialog(activity); + // if the progress dialog is displayed from the application class, design is missing + // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay + ContextThemeWrapper context = new ContextThemeWrapper(activity, + R.style.Theme_AppCompat_Light); + + ProgressDialog dialog = new ProgressDialog(context); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setCancelable(false); dialog.setCanceledOnTouchOutside(false); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java new file mode 100644 index 000000000..e3c9804bb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.AttributeSet; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; + +public class CertifyKeySpinner extends KeySpinner { + private long mHiddenMasterKeyId = Constants.key.none; + + public CertifyKeySpinner(Context context) { + super(context); + } + + public CertifyKeySpinner(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CertifyKeySpinner(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setHiddenMasterKeyId(long hiddenMasterKeyId) { + this.mHiddenMasterKeyId = hiddenMasterKeyId; + reload(); + } + + @Override + public Loader<Cursor> onCreateLoader() { + // 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 = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_EXPIRED, + KeychainContract.KeyRings.HAS_CERTIFY, + KeychainContract.KeyRings.HAS_ANY_SECRET + }; + + String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " + + KeychainContract.KeyRings.HAS_CERTIFY + " NOT NULL AND " + + KeychainContract.KeyRings.IS_REVOKED + " = 0 AND " + + KeychainContract.KeyRings.IS_EXPIRED + " = 0 AND " + KeychainDatabase.Tables.KEYS + "." + + KeychainContract.KeyRings.MASTER_KEY_ID + " != " + mHiddenMasterKeyId; + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getContext(), baseUri, projection, where, null, null); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 20b9570bb..ceb3f665f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -111,7 +111,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView { protected void onAttachedToWindow() { super.onAttachedToWindow(); if (getContext() instanceof FragmentActivity) { - ((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() { + ((FragmentActivity) getContext()).getSupportLoaderManager().initLoader(hashCode(), null, new LoaderManager.LoaderCallbacks<Cursor>() { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { // These are the rows that we will retrieve. @@ -143,6 +143,8 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView { swapCursor(null); } }); + } else { + Log.e(Constants.TAG, "EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + getContext().getClass()); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java index 31e01a7fb..b456b61ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java @@ -24,7 +24,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -38,9 +38,7 @@ import org.sufficientlysecure.keychain.R; android:layout_width="wrap_content" android:layout_height="wrap_content" custom:foldedLabel="@string/TEXT_TO_DISPLAY_WHEN_FOLDED" - custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED" - custom:foldedIcon="ICON_NAME_FROM_FontAwesomeText_TO_USE_WHEN_FOLDED" - custom:unFoldedIcon="ICON_NAME_FROM_FontAwesomeText_TO_USE_WHEN_UNFOLDED"> + custom:unFoldedLabel="@string/TEXT_TO_DISPLAY_WHEN_UNFOLDED"> <include layout="@layout/ELEMENTS_TO_BE_FOLDED"/> @@ -49,7 +47,7 @@ import org.sufficientlysecure.keychain.R; */ public class FoldableLinearLayout extends LinearLayout { - private ImageButton mFoldableIcon; + private ImageView mFoldableIcon; private boolean mFolded; private boolean mHasMigrated = false; private Integer mShortAnimationDuration = null; @@ -139,7 +137,7 @@ public class FoldableLinearLayout extends LinearLayout { } private void initialiseInnerViews() { - mFoldableIcon = (ImageButton) mFoldableLayout.findViewById(R.id.foldableIcon); + mFoldableIcon = (ImageView) mFoldableLayout.findViewById(R.id.foldableIcon); mFoldableIcon.setImageResource(R.drawable.ic_action_expand); mFoldableTextView = (TextView) mFoldableLayout.findViewById(R.id.foldableText); mFoldableTextView.setText(mFoldedLabel); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java new file mode 100644 index 000000000..3a31fc9f4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.util.Log; + +public abstract class KeySpinner extends Spinner { + public interface OnKeyChangedListener { + public void onKeyChanged(long masterKeyId); + } + + private long mSelectedKeyId; + private SelectKeyAdapter mAdapter = new SelectKeyAdapter(); + private OnKeyChangedListener mListener; + + public KeySpinner(Context context) { + super(context); + initView(); + } + + public KeySpinner(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public KeySpinner(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initView(); + } + + private void initView() { + setAdapter(mAdapter); + super.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (mListener != null) { + mListener.onKeyChanged(id); + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + if (mListener != null) { + mListener.onKeyChanged(Constants.key.none); + } + } + }); + } + + public abstract Loader<Cursor> onCreateLoader(); + + @Override + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + throw new UnsupportedOperationException(); + } + + public void setOnKeyChangedListener(OnKeyChangedListener listener) { + mListener = listener; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + reload(); + } + + public void reload() { + if (getContext() instanceof FragmentActivity) { + ((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(hashCode(), null, new LoaderManager.LoaderCallbacks<Cursor>() { + @Override + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + return KeySpinner.this.onCreateLoader(); + } + + @Override + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + mAdapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader<Cursor> loader) { + mAdapter.swapCursor(null); + } + }); + } else { + Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass()); + } + } + + public long getSelectedKeyId() { + return mSelectedKeyId; + } + + public void setSelectedKeyId(long selectedKeyId) { + this.mSelectedKeyId = selectedKeyId; + } + + private class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter { + private CursorAdapter inner; + private int mIndexUserId; + private int mIndexKeyId; + private int mIndexMasterKeyId; + + public SelectKeyAdapter() { + inner = new CursorAdapter(null, null, 0) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return View.inflate(getContext(), R.layout.keyspinner_key, null); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId)); + ((TextView) view.findViewById(android.R.id.title)).setText(userId[2] == null ? userId[0] : (userId[0] + " (" + userId[2] + ")")); + ((TextView) view.findViewById(android.R.id.text1)).setText(userId[1]); + ((TextView) view.findViewById(android.R.id.text2)).setText(PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId))); + } + + @Override + public long getItemId(int position) { + try { + return ((Cursor) getItem(position)).getLong(mIndexMasterKeyId); + } catch (Exception e) { + // This can happen on concurrent modification :( + return Constants.key.none; + } + } + }; + } + + public Cursor swapCursor(Cursor newCursor) { + if (newCursor == null) return inner.swapCursor(null); + + mIndexKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.KEY_ID); + mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID); + mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID); + if (newCursor.moveToFirst()) { + do { + if (newCursor.getLong(mIndexMasterKeyId) == mSelectedKeyId) { + setSelection(newCursor.getPosition() + 1); + } + } while (newCursor.moveToNext()); + } + return inner.swapCursor(newCursor); + } + + @Override + public int getCount() { + return inner.getCount() + 1; + } + + @Override + public Object getItem(int position) { + if (position == 0) return null; + return inner.getItem(position - 1); + } + + @Override + public long getItemId(int position) { + if (position == 0) return Constants.key.none; + return inner.getItemId(position - 1); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + try { + View v = getDropDownView(position, convertView, parent); + v.findViewById(android.R.id.text1).setVisibility(View.GONE); + return v; + } catch (NullPointerException e) { + // This is for the preview... + return View.inflate(getContext(), android.R.layout.simple_list_item_1, null); + } + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + View v; + if (position == 0) { + if (convertView == null) { + v = inner.newView(null, null, parent); + } else { + v = convertView; + } + ((TextView) v.findViewById(android.R.id.title)).setText("None"); + v.findViewById(android.R.id.text1).setVisibility(View.GONE); + v.findViewById(android.R.id.text2).setVisibility(View.GONE); + } else { + v = inner.getView(position - 1, convertView, parent); + v.findViewById(android.R.id.text1).setVisibility(View.VISIBLE); + v.findViewById(android.R.id.text2).setVisibility(View.VISIBLE); + } + return v; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java deleted file mode 100644 index a48d2a026..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/NoSwipeWrapContentViewPager.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; - -public class NoSwipeWrapContentViewPager extends android.support.v4.view.ViewPager { - public NoSwipeWrapContentViewPager(Context context) { - super(context); - } - - public NoSwipeWrapContentViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - - int height; - View child = getChildAt(getCurrentItem()); - if (child != null) { - child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - height = child.getMeasuredHeight(); - } else { - height = 0; - } - - heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent arg0) { - // Never allow swiping to switch between pages - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // Never allow swiping to switch between pages - return false; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java new file mode 100644 index 000000000..a1ed2c065 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.util.AttributeSet; +import org.sufficientlysecure.keychain.provider.KeychainContract; + +public class SignKeySpinner extends KeySpinner { + public SignKeySpinner(Context context) { + super(context); + } + + public SignKeySpinner(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SignKeySpinner(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public Loader<Cursor> onCreateLoader() { + // 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 = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_EXPIRED, + KeychainContract.KeyRings.HAS_SIGN, + KeychainContract.KeyRings.HAS_ANY_SECRET + }; + + String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " + KeychainContract.KeyRings.HAS_SIGN + " NOT NULL AND " + + KeychainContract.KeyRings.IS_REVOKED + " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0"; + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getContext(), baseUri, projection, where, null, null); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java index 9acc7a73b..99db634ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java @@ -50,7 +50,6 @@ public class AlgorithmNames { mEncryptionNames.put(PGPEncryptedData.TRIPLE_DES, "Triple DES"); mEncryptionNames.put(PGPEncryptedData.IDEA, "IDEA"); - mHashNames.put(HashAlgorithmTags.MD5, "MD5"); mHashNames.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160"); mHashNames.put(HashAlgorithmTags.SHA1, "SHA-1"); mHashNames.put(HashAlgorithmTags.SHA224, "SHA-224"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java index 70c7d80fe..48f10d4b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java @@ -17,21 +17,16 @@ package org.sufficientlysecure.keychain.util; -public class Choice { +public class Choice <E> { private String mName; - private int mId; + private E mId; - public Choice() { - mId = -1; - mName = ""; - } - - public Choice(int id, String name) { + public Choice(E id, String name) { mId = id; mName = name; } - public int getId() { + public E getId() { return mId; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileImportCache.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileImportCache.java index 5a4bf5311..09275fc95 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileImportCache.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileImportCache.java @@ -46,10 +46,11 @@ public class FileImportCache<E extends Parcelable> { private Context mContext; - private static final String FILENAME = "key_import.pcl"; + private final String mFilename; - public FileImportCache(Context context) { - this.mContext = context; + public FileImportCache(Context context, String filename) { + mContext = context; + mFilename = filename; } public void writeCache(ArrayList<E> selectedEntries) throws IOException { @@ -64,7 +65,7 @@ public class FileImportCache<E extends Parcelable> { throw new IOException("cache dir is null!"); } - File tempFile = new File(mContext.getCacheDir(), FILENAME); + File tempFile = new File(mContext.getCacheDir(), mFilename); DataOutputStream oos = new DataOutputStream(new FileOutputStream(tempFile)); @@ -91,6 +92,10 @@ public class FileImportCache<E extends Parcelable> { } public Iterator<E> readCache() throws IOException { + return readCache(true); + } + + public Iterator<E> readCache(final boolean deleteAfterRead) throws IOException { File cacheDir = mContext.getCacheDir(); if (cacheDir == null) { @@ -98,7 +103,7 @@ public class FileImportCache<E extends Parcelable> { throw new IOException("cache dir is null!"); } - final File tempFile = new File(cacheDir, FILENAME); + final File tempFile = new File(cacheDir, mFilename); final DataInputStream ois = new DataInputStream(new FileInputStream(tempFile)); return new Iterator<E>() { @@ -165,7 +170,10 @@ public class FileImportCache<E extends Parcelable> { if (!closed) { try { ois.close(); - tempFile.delete(); + if (deleteAfterRead) { + //noinspection ResultOfMethodCallIgnored + tempFile.delete(); + } } catch (IOException e) { // nvm } @@ -176,4 +184,17 @@ public class FileImportCache<E extends Parcelable> { }; } + + public boolean delete() throws IOException { + + File cacheDir = mContext.getCacheDir(); + if (cacheDir == null) { + // https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU + throw new IOException("cache dir is null!"); + } + + final File tempFile = new File(cacheDir, mFilename); + return tempFile.delete(); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java new file mode 100644 index 000000000..4bb4ca5de --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java @@ -0,0 +1,29 @@ +package org.sufficientlysecure.keychain.util; + +import org.sufficientlysecure.keychain.pgp.Progressable; + +/** This is a simple variant of ProgressScaler which shows a fixed progress message, ignoring + * the provided ones. + */ +public class ProgressFixedScaler extends ProgressScaler { + + final int mResId; + + public ProgressFixedScaler(Progressable wrapped, int from, int to, int max, int resId) { + super(wrapped, from, to, max); + mResId = resId; + } + + public void setProgress(int resourceId, int progress, int max) { + if (mWrapped != null) { + mWrapped.setProgress(mResId, mFrom + progress * (mTo - mFrom) / max, mMax); + } + } + + public void setProgress(String message, int progress, int max) { + if (mWrapped != null) { + mWrapped.setProgress(mResId, mFrom + progress * (mTo - mFrom) / max, mMax); + } + } + +} |