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 5ce0b11dd..ebc5a7868 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -33,6 +33,9 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.OperationResultParcel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; +import org.sufficientlysecure.keychain.service.OperationResults.ImportResult; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; @@ -123,12 +126,9 @@ public class PgpImportExport { } } - /** - * Imports keys from given data. If keyIds is given only those are imported - */ - public Bundle importKeyRings(List entries) + /** Imports keys from given data. If keyIds is given only those are imported */ + public ImportResult importKeyRings(List entries) throws PgpGeneralException, PGPException, IOException { - Bundle returnData = new Bundle(); updateProgress(R.string.progress_importing, 0, 100); @@ -152,14 +152,8 @@ public class PgpImportExport { } } - mProviderHelper.resetLog(); OperationResultParcel result = mProviderHelper.savePublicKeyRing(key); - for(OperationResultParcel.LogEntryParcel loge : result.mLog) { - Log.d(Constants.TAG, - loge.mIndent - + new String(new char[loge.mIndent]).replace("\0", " ") - + mContext.getString(loge.mType.getMsgId(), (Object[]) loge.mParameters)); - } + newKeys += 1; } catch (PgpGeneralException e) { @@ -171,11 +165,30 @@ public class PgpImportExport { updateProgress(position / entries.size() * 100, 100); } - returnData.putInt(KeychainIntentService.RESULT_IMPORT_ADDED, newKeys); - returnData.putInt(KeychainIntentService.RESULT_IMPORT_UPDATED, oldKeys); - returnData.putInt(KeychainIntentService.RESULT_IMPORT_BAD, badKeys); + OperationLog log = mProviderHelper.getLog(); + int resultType; + // Any key imported - overall success + if (newKeys > 0 || oldKeys > 0) { + if (badKeys > 0) { + resultType = ImportResult.RESULT_PARTIAL_WITH_ERRORS; + } else if (log.containsWarnings()) { + resultType = ImportResult.RESULT_OK_WITH_WARNINGS; + } else if (newKeys > 0 && oldKeys > 0) { + resultType = ImportResult.RESULT_OK_BOTHKEYS; + } else if (newKeys > 0) { + resultType = ImportResult.RESULT_OK_NEWKEYS; + } else { + resultType = ImportResult.RESULT_OK_UPDATED; + } + // No keys imported, overall failure + } else if (badKeys > 0) { + resultType = ImportResult.RESULT_FAIL_ERROR; + } else { + resultType = ImportResult.RESULT_FAIL_NOTHING; + } + + return new ImportResult(resultType, log, newKeys, oldKeys, badKeys); - return returnData; } public Bundle exportKeyRings(ArrayList publicKeyRingMasterIds, 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 5c8bf6752..ab4672a98 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -29,9 +29,10 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.OperationResultParcel; -import org.sufficientlysecure.keychain.pgp.OperationResultParcel.LogType; -import org.sufficientlysecure.keychain.pgp.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.service.OperationResultParcel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -48,6 +49,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.service.OperationResults; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -61,18 +63,27 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +/** This class contains high level methods for database access. Despite its + * name, it is not only a helper but actually the main interface for all + * synchronous database operations. + * + * Operations in this class write logs (TODO). These can be obtained from the + * OperationResultParcel return values directly, but are also accumulated over + * the lifetime of the executing ProviderHelper object unless the resetLog() + * method is called to start a new one specifically. + * + */ public class ProviderHelper { private final Context mContext; private final ContentResolver mContentResolver; - private final ArrayList mLog; + private OperationLog mLog; private int mIndent; public ProviderHelper(Context context) { - this(context, new ArrayList(), 0); + this(context, new OperationLog(), 0); } - public ProviderHelper(Context context, ArrayList log, - int indent) { + public ProviderHelper(Context context, OperationLog log, int indent) { mContext = context; mContentResolver = context.getContentResolver(); mLog = log; @@ -81,11 +92,16 @@ public class ProviderHelper { public void resetLog() { if(mLog != null) { - mLog.clear(); + // Start a new log (leaving the old one intact) + mLog = new OperationLog(); mIndent = 0; } } + public OperationLog getLog() { + return mLog; + } + public static class NotFoundException extends Exception { public NotFoundException() { } @@ -97,12 +113,12 @@ public class ProviderHelper { public void log(LogLevel level, LogType type) { if(mLog != null) { - mLog.add(new OperationResultParcel.LogEntryParcel(level, type, null, mIndent)); + mLog.add(level, type, null, mIndent); } } public void log(LogLevel level, LogType type, String[] parameters) { if(mLog != null) { - mLog.add(new OperationResultParcel.LogEntryParcel(level, type, parameters, mIndent)); + mLog.add(level, type, parameters, mIndent); } } @@ -258,6 +274,9 @@ public class ProviderHelper { return new OperationResultParcel(1, mLog); } + // Canonicalize this key, to assert a number of assumptions made about the key. + keyRing = keyRing.canonicalize(mLog); + UncachedPublicKey masterKey = keyRing.getPublicKey(); long masterKeyId = masterKey.getKeyId(); log(LogLevel.INFO, LogType.MSG_IP_IMPORTING, @@ -282,34 +301,80 @@ public class ProviderHelper { log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_FAIL); } - // insert new version of this keyRing - ContentValues values = new ContentValues(); - values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); try { - values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); - } catch (IOException e) { - log(LogLevel.ERROR, LogType.MSG_IP_ENCODE_FAIL); - return new OperationResultParcel(1, mLog); - } - // save all keys and userIds included in keyRing object in database - ArrayList operations = new ArrayList(); - - try { + // save all keys and userIds included in keyRing object in database + ArrayList operations = new ArrayList(); log(LogLevel.INFO, LogType.MSG_IP_INSERT_KEYRING); - Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); - operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); + { // insert keyring + // insert new version of this keyRing + ContentValues values = new ContentValues(); + values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); + try { + values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded()); + } catch (IOException e) { + log(LogLevel.ERROR, LogType.MSG_IP_ENCODE_FAIL); + return new OperationResultParcel(1, mLog); + } + + Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); + operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); + } log(LogLevel.INFO, LogType.MSG_IP_INSERT_SUBKEYS); mIndent += 1; - int rank = 0; - for (UncachedPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - log(LogLevel.DEBUG, LogType.MSG_IP_INSERT_SUBKEY, new String[] { - PgpKeyHelper.convertKeyIdToHex(key.getKeyId()) - }); - operations.add(buildPublicKeyOperations(masterKeyId, key, rank)); - ++rank; + { // insert subkeys + Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); + int rank = 0; + for (UncachedPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + log(LogLevel.DEBUG, LogType.MSG_IP_SUBKEY, new String[]{ + PgpKeyHelper.convertKeyIdToHex(key.getKeyId()) + }); + mIndent += 1; + + ContentValues values = new ContentValues(); + values.put(Keys.MASTER_KEY_ID, masterKeyId); + values.put(Keys.RANK, rank); + + values.put(Keys.KEY_ID, key.getKeyId()); + values.put(Keys.KEY_SIZE, key.getBitStrength()); + values.put(Keys.ALGORITHM, key.getAlgorithm()); + values.put(Keys.FINGERPRINT, key.getFingerprint()); + + boolean c = key.canCertify(), s = key.canSign(), e = key.canEncrypt(); + values.put(Keys.CAN_CERTIFY, c); + values.put(Keys.CAN_SIGN, s); + values.put(Keys.CAN_ENCRYPT, e); + values.put(Keys.IS_REVOKED, key.isRevoked()); + log(LogLevel.DEBUG, LogType.MSG_IP_SUBKEY_FLAGS, new String[] { "X" }); + + Date creation = key.getCreationTime(); + values.put(Keys.CREATION, creation.getTime() / 1000); + if (creation.after(new Date())) { + log(LogLevel.ERROR, LogType.MSG_IP_SUBKEY_FUTURE, new String[] { + creation.toString() + }); + return new OperationResultParcel(1, mLog); + } + Date expiryDate = key.getExpiryTime(); + if (expiryDate != null) { + values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + if (key.isExpired()) { + log(LogLevel.INFO, LogType.MSG_IP_SUBKEY_EXPIRED, new String[] { + expiryDate.toString() + }); + } else { + log(LogLevel.DEBUG, LogType.MSG_IP_SUBKEY_EXPIRES, new String[] { + expiryDate.toString() + }); + } + } + + operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); + ++rank; + mIndent -= 1; + } } mIndent -= 1; @@ -374,7 +439,12 @@ public class ProviderHelper { // save certificate as primary self-cert item.selfCert = cert; item.isPrimary = cert.isPrimaryUserId(); - item.isRevoked = cert.isRevocation(); + if (cert.isRevocation()) { + item.isRevoked = true; + log(LogLevel.INFO, LogType.MSG_IP_UID_REVOKED); + } else { + item.isRevoked = false; + } } @@ -500,7 +570,7 @@ public class ProviderHelper { long masterKeyId = keyRing.getMasterKeyId(); log(LogLevel.INFO, LogType.MSG_IS_IMPORTING, - new String[]{ Long.toString(masterKeyId) }); + new String[]{Long.toString(masterKeyId)}); // save secret keyring try { @@ -575,37 +645,6 @@ public class ProviderHelper { saveSecretKeyRing(secRing); } - /** - * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing - */ - private ContentProviderOperation - buildPublicKeyOperations(long masterKeyId, UncachedPublicKey key, int rank) throws IOException { - - ContentValues values = new ContentValues(); - values.put(Keys.MASTER_KEY_ID, masterKeyId); - values.put(Keys.RANK, rank); - - values.put(Keys.KEY_ID, key.getKeyId()); - values.put(Keys.KEY_SIZE, key.getBitStrength()); - values.put(Keys.ALGORITHM, key.getAlgorithm()); - values.put(Keys.FINGERPRINT, key.getFingerprint()); - - values.put(Keys.CAN_CERTIFY, key.canCertify()); - values.put(Keys.CAN_SIGN, key.canSign()); - values.put(Keys.CAN_ENCRYPT, key.canEncrypt()); - values.put(Keys.IS_REVOKED, key.maybeRevoked()); - - values.put(Keys.CREATION, key.getCreationTime().getTime() / 1000); - Date expiryDate = key.getExpiryTime(); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); - } - - Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); - - return ContentProviderOperation.newInsert(uri).withValues(values).build(); - } - /** * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ 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 498b963f2..f2231fbb5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -174,14 +174,11 @@ public class KeychainIntentService extends IntentService public static final String RESULT_DECRYPTED_BYTES = "decrypted_data"; public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature"; - // import - public static final String RESULT_IMPORT_ADDED = "added"; - public static final String RESULT_IMPORT_UPDATED = "updated"; - public static final String RESULT_IMPORT_BAD = "bad"; - // export public static final String RESULT_EXPORT = "exported"; + public static final String RESULT = "result"; + Messenger mMessenger; private boolean mIsCanceled; @@ -648,7 +645,10 @@ public class KeychainIntentService extends IntentService List entries = data.getParcelableArrayList(IMPORT_KEY_LIST); PgpImportExport pgpImportExport = new PgpImportExport(this, this); - Bundle resultData = pgpImportExport.importKeyRings(entries); + OperationResults.ImportResult result = pgpImportExport.importKeyRings(entries); + + Bundle resultData = new Bundle(); + resultData.putParcelable(RESULT, result); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); } catch (Exception e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java similarity index 74% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java index 216e4b497..a1a1d0067 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -1,9 +1,10 @@ -package org.sufficientlysecure.keychain.pgp; +package org.sufficientlysecure.keychain.service; import android.os.Parcel; import android.os.Parcelable; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.IterableIterator; import java.util.ArrayList; @@ -17,33 +18,45 @@ import java.util.ArrayList; * */ public class OperationResultParcel implements Parcelable { - /** Holds the overall result. A value of 0 is considered a success, all - * other values may represent failure or varying degrees of success. */ + /** Holds the overall result, the number specifying varying degrees of success. + * Values smaller than 100 are considered an overall success. */ final int mResult; - /// A list of log entries tied to the operation result. - final ArrayList mLog; + public static final int RESULT_OK = 0; + public static final int RESULT_ERROR = 100; - public OperationResultParcel(int result, ArrayList log) { + /// A list of log entries tied to the operation result. + final OperationLog mLog; + + public OperationResultParcel(int result, OperationLog log) { mResult = result; mLog = log; } public OperationResultParcel(Parcel source) { mResult = source.readInt(); - mLog = source.createTypedArrayList(LogEntryParcel.CREATOR); + mLog = new OperationLog(); + mLog.addAll(source.createTypedArrayList(LogEntryParcel.CREATOR)); + } + + public int getResult() { + return mResult; } public boolean isSuccessful() { - return mResult == 0; + return mResult < 100; + } + + public OperationLog getLog() { + return mLog; } /** One entry in the log. */ public static class LogEntryParcel implements Parcelable { - final LogLevel mLevel; - final LogType mType; - final String[] mParameters; - final int mIndent; + public final LogLevel mLevel; + public final LogType mType; + public final String[] mParameters; + public final int mIndent; public LogEntryParcel(LogLevel level, LogType type, String[] parameters, int indent) { mLevel = level; @@ -98,10 +111,14 @@ public class OperationResultParcel implements Parcelable { MSG_IP_FAIL_REMOTE_EX (R.string.msg_ip_fail_remote_ex), MSG_IP_IMPORTING (R.string.msg_ip_importing), MSG_IP_INSERT_KEYRING (R.string.msg_ip_insert_keyring), - MSG_IP_INSERT_SUBKEY (R.string.msg_ip_insert_subkey), MSG_IP_INSERT_SUBKEYS (R.string.msg_ip_insert_subkeys), MSG_IP_PRESERVING_SECRET (R.string.msg_ip_preserving_secret), MSG_IP_REINSERT_SECRET (R.string.msg_ip_reinsert_secret), + MSG_IP_SUBKEY (R.string.msg_ip_subkey), + MSG_IP_SUBKEY_EXPIRED (R.string.msg_ip_subkey_expired), + MSG_IP_SUBKEY_EXPIRES (R.string.msg_ip_subkey_expires), + MSG_IP_SUBKEY_FLAGS (R.string.msg_ip_subkey_flags), + MSG_IP_SUBKEY_FUTURE (R.string.msg_ip_subkey_future), MSG_IP_SUCCESS (R.string.msg_ip_success), MSG_IP_TRUST_RETRIEVE (R.string.msg_ip_trust_retrieve), MSG_IP_TRUST_USING (R.string.msg_ip_trust_using), @@ -113,6 +130,7 @@ public class OperationResultParcel implements Parcelable { MSG_IP_UID_CLASSIFYING (R.string.msg_ip_uid_classifying), MSG_IP_UID_INSERT (R.string.msg_ip_uid_insert), MSG_IP_UID_PROCESSING (R.string.msg_ip_uid_processing), + MSG_IP_UID_REVOKED (R.string.msg_ip_uid_revoked), MSG_IP_UID_SELF_BAD (R.string.msg_ip_uid_self_bad), MSG_IP_UID_SELF_GOOD (R.string.msg_ip_uid_self_good), MSG_IP_UID_SELF_IGNORING_OLD (R.string.msg_ip_uid_self_ignoring_old), @@ -138,6 +156,7 @@ public class OperationResultParcel implements Parcelable { /** Enumeration of possible log levels. */ public static enum LogLevel { + OK, DEBUG, INFO, WARN, @@ -166,4 +185,23 @@ public class OperationResultParcel implements Parcelable { } }; + public static class OperationLog extends ArrayList { + + /// Simple convenience method + public void add(LogLevel level, LogType type, String[] parameters, int indent) { + add(new OperationResultParcel.LogEntryParcel(level, type, parameters, indent)); + } + + public boolean containsWarnings() { + int warn = LogLevel.WARN.ordinal(); + for(LogEntryParcel entry : new IterableIterator(iterator())) { + if (entry.mLevel.ordinal() >= warn) { + return true; + } + } + return false; + } + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java new file mode 100644 index 000000000..e08b50500 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java @@ -0,0 +1,63 @@ +package org.sufficientlysecure.keychain.service; + +import android.os.Parcel; + +public abstract class OperationResults { + + public static class ImportResult extends OperationResultParcel { + + public final int mNewKeys, mUpdatedKeys, mBadKeys; + + // Operation ok, at least one new key (no warnings) + public static final int RESULT_OK_NEWKEYS = 1; + // Operation ok, at least one new and one updated key (no warnings) + public static final int RESULT_OK_BOTHKEYS = 2; + // Operation ok, no new keys but upated ones (no warnings) + public static final int RESULT_OK_UPDATED = 3; + // Operation ok, but with warnings + public static final int RESULT_OK_WITH_WARNINGS = 4; + + // Operation partially ok, but at least one key failed! + public static final int RESULT_PARTIAL_WITH_ERRORS = 50; + + // Operation failed, errors thrown and no new keys imported + public static final int RESULT_FAIL_ERROR = 100; + // Operation failed, no keys to import... + public static final int RESULT_FAIL_NOTHING = 101; + + public ImportResult(Parcel source) { + super(source); + mNewKeys = source.readInt(); + mUpdatedKeys = source.readInt(); + mBadKeys = source.readInt(); + } + + public ImportResult(int result, OperationLog log, + int newKeys, int updatedKeys, int badKeys) { + super(result, log); + mNewKeys = newKeys; + mUpdatedKeys = updatedKeys; + mBadKeys = badKeys; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mNewKeys); + dest.writeInt(mUpdatedKeys); + dest.writeInt(mBadKeys); + } + + public static Creator CREATOR = new Creator() { + public ImportResult createFromParcel(final Parcel source) { + return new ImportResult(source); + } + + public ImportResult[] newArray(final int size) { + return new ImportResult[size]; + } + }; + + } + +} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 6f21900fe..86219da70 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -529,13 +529,17 @@ Operation failed due to i/o error Operation failed due to database error Operation failed due to internal error - Importing public keyring + Importing public keyring %s Inserting keyring data - Inserting subkey %s Inserting subkeys Preserving available secret key - Re-inserting secret key + Processing subkey %s + Subkey expired on %s + Subkey expires on %s + Subkey flags: %s + Subkey creation date lies in the future! (%s) Successfully inserted public keyring + Re-inserting secret key Retrieving trusted keys Using %s trusted keys Secret key available, self certificates are trusted @@ -546,6 +550,7 @@ Classifying user ids Inserting user ids Processing user id %s + Found uid revocation certificate Bad self certificate encountered! Found good self certificate Ignoring older self certificate @@ -558,5 +563,6 @@ Marked %s as available Marked %s as stripped Successfully inserted secret keyring + Log