diff options
38 files changed, 1613 insertions, 526 deletions
diff --git a/.gitmodules b/.gitmodules index 20a0f60e0..a549f6cec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,6 +25,9 @@ [submodule "extern/openkeychain-api-lib"] path = extern/openkeychain-api-lib url = https://github.com/open-keychain/openkeychain-api-lib.git +[submodule "extern/SuperToasts"] + path = extern/SuperToasts + url = https://github.com/open-keychain/SuperToasts.git [submodule "extern/dnsjava"] path = extern/dnsjava url = https://github.com/open-keychain/dnsjava.git diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index dca9faba6..32b60e915 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -26,6 +26,7 @@ dependencies { compile project(':extern:spongycastle:pkix') compile project(':extern:spongycastle:prov') compile project(':extern:AppMsg:library') + compile project(':extern:SuperToasts:supertoasts') compile project(':extern:dnsjava') // Dependencies for the `instrumentTest` task, make sure to list all your global dependencies here as well @@ -47,6 +48,8 @@ dependencies { androidTestCompile project(':extern:spongycastle:pkix') androidTestCompile project(':extern:spongycastle:prov') androidTestCompile project(':extern:AppMsg:library') + androidTestCompile project(':extern:SuperToasts:supertoasts') + } android { diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 31c809334..b3a4d5960 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -445,6 +445,12 @@ </intent-filter> </service> + <activity + android:name=".ui.LogDisplayActivity" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:label="@string/title_log_display" + android:exported="false" /> + <service android:name=".service.DummyAccountService"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> 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 7a7c89301..47265c3aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -251,7 +251,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.mKeyId = key.getKeyId(); this.mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId); - this.mRevoked = key.maybeRevoked(); + this.mRevoked = key.isRevoked(); this.mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint()); this.mBitStrength = key.getBitStrength(); final int algorithm = key.getAlgorithm(); 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 5da6c4cd3..fdf561aaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ParcelableKeyRing.java @@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.keyimport; import android.os.Parcel; import android.os.Parcelable; -/** This is a trivial wrapper around UncachedKeyRing which implements Parcelable. It exists +/** This is a trivial wrapper around keyring bytes which implements Parcelable. It exists * for the sole purpose of keeping spongycastle and android imports in separate packages. */ public class ParcelableKeyRing implements Parcelable { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java deleted file mode 100644 index 216e4b497..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OperationResultParcel.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.sufficientlysecure.keychain.pgp; - -import android.os.Parcel; -import android.os.Parcelable; - -import org.sufficientlysecure.keychain.R; - -import java.util.ArrayList; - -/** Represent the result of an operation. - * - * This class holds a result and the log of an operation. It can be subclassed - * to include typed additional information specific to the operation. To keep - * the class structure (somewhat) simple, this class contains an exhaustive - * list (ie, enum) of all possible log types, which should in all cases be tied - * to string resource ids. - * - */ -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. */ - final int mResult; - - /// A list of log entries tied to the operation result. - final ArrayList<LogEntryParcel> mLog; - - public OperationResultParcel(int result, ArrayList<LogEntryParcel> log) { - mResult = result; - mLog = log; - } - - public OperationResultParcel(Parcel source) { - mResult = source.readInt(); - mLog = source.createTypedArrayList(LogEntryParcel.CREATOR); - } - - public boolean isSuccessful() { - return mResult == 0; - } - - /** One entry in the log. */ - public static class LogEntryParcel implements Parcelable { - final LogLevel mLevel; - final LogType mType; - final String[] mParameters; - final int mIndent; - - public LogEntryParcel(LogLevel level, LogType type, String[] parameters, int indent) { - mLevel = level; - mType = type; - mParameters = parameters; - mIndent = indent; - } - public LogEntryParcel(LogLevel level, LogType type, String[] parameters) { - this(level, type, parameters, 0); - } - - public LogEntryParcel(Parcel source) { - mLevel = LogLevel.values()[source.readInt()]; - mType = LogType.values()[source.readInt()]; - mParameters = source.createStringArray(); - mIndent = source.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mLevel.ordinal()); - dest.writeInt(mType.ordinal()); - dest.writeStringArray(mParameters); - dest.writeInt(mIndent); - } - - public static final Creator<LogEntryParcel> CREATOR = new Creator<LogEntryParcel>() { - public LogEntryParcel createFromParcel(final Parcel source) { - return new LogEntryParcel(source); - } - - public LogEntryParcel[] newArray(final int size) { - return new LogEntryParcel[size]; - } - }; - - } - - public static enum LogType { - MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch), - MSG_IP_BAD_TYPE_SECRET (R.string.msg_ip_bad_type_secret), - MSG_IP_DELETE_OLD_FAIL (R.string.msg_ip_delete_old_fail), - MSG_IP_DELETE_OLD_OK (R.string.msg_ip_delete_old_ok), - MSG_IP_ENCODE_FAIL (R.string.msg_ip_encode_fail), - MSG_IP_FAIL_IO_EXC (R.string.msg_ip_fail_io_exc), - MSG_IP_FAIL_OP_EX (R.string.msg_ip_fail_op_ex), - 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_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), - MSG_IP_TRUST_USING_SEC (R.string.msg_ip_trust_using_sec), - MSG_IP_UID_CERT_BAD (R.string.msg_ip_uid_cert_bad), - MSG_IP_UID_CERT_ERROR (R.string.msg_ip_uid_cert_error), - MSG_IP_UID_CERT_GOOD (R.string.msg_ip_uid_cert_good), - MSG_IP_UID_CERTS_UNKNOWN (R.string.msg_ip_uid_certs_unknown), - 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_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), - MSG_IP_UID_SELF_NEWER (R.string.msg_ip_uid_self_newer), - MSG_IS_BAD_TYPE_PUBLIC (R.string.msg_is_bad_type_public), - MSG_IS_IMPORTING (R.string.msg_is_importing), - MSG_IS_IMPORTING_SUBKEYS (R.string.msg_is_importing_subkeys), - MSG_IS_IO_EXCPTION (R.string.msg_is_io_excption), - MSG_IS_SUBKEY_NONEXISTENT (R.string.msg_is_subkey_nonexistent), - MSG_IS_SUBKEY_OK (R.string.msg_is_subkey_ok), - MSG_IS_SUBKEY_STRIPPED (R.string.msg_is_subkey_stripped), - MSG_IS_SUCCESS (R.string.msg_is_success), - ; - - private final int mMsgId; - LogType(int msgId) { - mMsgId = msgId; - } - public int getMsgId() { - return mMsgId; - } - } - - /** Enumeration of possible log levels. */ - public static enum LogLevel { - DEBUG, - INFO, - WARN, - /** If any ERROR log entry is included in the result, the overall operation should have failed. */ - ERROR, - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mResult); - dest.writeTypedList(mLog); - } - - public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() { - public OperationResultParcel createFromParcel(final Parcel source) { - return new OperationResultParcel(source); - } - - public OperationResultParcel[] newArray(final int size) { - return new OperationResultParcel[size]; - } - }; - -} 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..e1967429a 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.OperationLog; +import org.sufficientlysecure.keychain.service.OperationResults.ImportResult; +import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; @@ -55,10 +58,6 @@ public class PgpImportExport { private ProviderHelper mProviderHelper; - public static final int RETURN_OK = 0; - public static final int RETURN_BAD = -2; - public static final int RETURN_UPDATED = 1; - public PgpImportExport(Context context, Progressable progressable) { super(); this.mContext = context; @@ -115,26 +114,20 @@ public class PgpImportExport { if (aos != null) { aos.close(); } - if (bos != null) { - bos.close(); - } + bos.close(); } catch (IOException e) { + // this is just a finally thing, no matter if it doesn't work out. } } } - /** - * Imports keys from given data. If keyIds is given only those are imported - */ - public Bundle importKeyRings(List<ParcelableKeyRing> entries) + /** Imports keys from given data. If keyIds is given only those are imported */ + public ImportResult importKeyRings(List<ParcelableKeyRing> entries) throws PgpGeneralException, PGPException, IOException { - Bundle returnData = new Bundle(); updateProgress(R.string.progress_importing, 0, 100); - int newKeys = 0; - int oldKeys = 0; - int badKeys = 0; + int newKeys = 0, oldKeys = 0, badKeys = 0; int position = 0; for (ParcelableKeyRing entry : entries) { @@ -152,15 +145,19 @@ 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)); + SaveKeyringResult result; + if (key.isSecret()) { + result = mProviderHelper.saveSecretKeyRing(key); + } else { + result = mProviderHelper.savePublicKeyRing(key); + } + if (!result.success()) { + badKeys += 1; + } else if (result.updated()) { + oldKeys += 1; + } else { + newKeys += 1; } - newKeys += 1; } catch (PgpGeneralException e) { Log.e(Constants.TAG, "Encountered bad key on import!", e); @@ -171,11 +168,31 @@ 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 = 0; + // special return case: no new keys at all + if (badKeys == 0 && newKeys == 0 && oldKeys == 0) { + resultType = ImportResult.RESULT_FAIL_NOTHING; + } else { + if (newKeys > 0) { + resultType |= ImportResult.RESULT_OK_NEWKEYS; + } + if (oldKeys > 0) { + resultType |= ImportResult.RESULT_OK_UPDATED; + } + if (badKeys > 0) { + resultType |= ImportResult.RESULT_WITH_ERRORS; + if (newKeys == 0 && oldKeys == 0) { + resultType |= ImportResult.RESULT_ERROR; + } + } + if (log.containsWarnings()) { + resultType |= ImportResult.RESULT_WITH_WARNINGS; + } + } + + return new ImportResult(resultType, log, newKeys, oldKeys, badKeys); - return returnData; } public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds, 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 1264c8c36..e309ed632 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -2,14 +2,23 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPKeyFlags; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPUtil; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -18,7 +27,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -169,4 +178,411 @@ public class UncachedKeyRing { return result; } + /** "Canonicalizes" a key, removing inconsistencies in the process. This operation can be + * applied to public keyrings only. + * + * More specifically: + * - Remove all non-verifying self-certificates + * - Remove all "future" self-certificates + * - Remove all certificates flagged as "local" + * - Remove all certificates which are superseded by a newer one on the same target + * + * After this cleaning, a number of checks are done: TODO implement + * - See if each subkey retains a valid self certificate + * - See if each user id retains a valid self certificate + * + * This operation writes an OperationLog which can be used as part of a OperationResultParcel. + * + * @return A canonicalized key + * + */ + public UncachedKeyRing canonicalize(OperationLog log, int indent) { + if (isSecret()) { + throw new RuntimeException("Tried to canonicalize non-secret keyring. " + + "This is a programming error and should never happen!"); + } + + log.add(LogLevel.START, LogType.MSG_KC, + new String[]{PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())}, indent); + indent += 1; + + final Date now = new Date(); + + int removedCerts = 0; + + PGPPublicKeyRing ring = (PGPPublicKeyRing) mRing; + PGPPublicKey masterKey = mRing.getPublicKey(); + final long masterKeyId = masterKey.getKeyID(); + + { + log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER, + new String[]{PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())}, indent); + indent += 1; + + PGPPublicKey modified = masterKey; + PGPSignature revocation = null; + for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getSignatures())) { + int type = zert.getSignatureType(); + + // Disregard certifications on user ids, we will deal with those later + if (type == PGPSignature.NO_CERTIFICATION + || type == PGPSignature.DEFAULT_CERTIFICATION + || type == PGPSignature.CASUAL_CERTIFICATION + || type == PGPSignature.POSITIVE_CERTIFICATION + || type == PGPSignature.CERTIFICATION_REVOCATION) { + continue; + } + WrappedSignature cert = new WrappedSignature(zert); + + if (type != PGPSignature.KEY_REVOCATION) { + // Unknown type, just remove + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE, new String[]{ + "0x" + Integer.toString(type, 16) + }, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + continue; + } + + if (cert.getCreationTime().after(now)) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + continue; + } + + if (cert.isLocal()) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + continue; + } + + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey)) { + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_ERR, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + continue; + } + + // first revocation? fine then. + if (revocation == null) { + revocation = zert; + // more revocations? at least one is superfluous, then. + } else if (revocation.getCreationTime().before(zert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, revocation); + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent); + revocation = zert; + } else { + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent); + } + } + + for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) { + PGPSignature selfCert = null; + revocation = null; + + // look through signatures for this specific key + for (PGPSignature zert : new IterableIterator<PGPSignature>( + masterKey.getSignaturesForID(userId))) { + WrappedSignature cert = new WrappedSignature(zert); + long certId = cert.getKeyId(); + + int type = zert.getSignatureType(); + if (type != PGPSignature.DEFAULT_CERTIFICATION + && type != PGPSignature.NO_CERTIFICATION + && type != PGPSignature.CASUAL_CERTIFICATION + && type != PGPSignature.POSITIVE_CERTIFICATION + && type != PGPSignature.CERTIFICATION_REVOCATION) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TYPE, + new String[] { + "0x" + Integer.toString(zert.getSignatureType(), 16) + }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + removedCerts += 1; + } + + if (cert.getCreationTime().after(now)) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + continue; + } + + if (cert.isLocal()) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + removedCerts += 1; + continue; + } + + // If this is a foreign signature, never mind any further + if (certId != masterKeyId) { + continue; + } + + // Otherwise, first make sure it checks out + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey, userId)) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD, + new String[] { userId }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + removedCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_ERR, + new String[] { userId }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + removedCerts += 1; + continue; + } + + switch (type) { + case PGPSignature.DEFAULT_CERTIFICATION: + case PGPSignature.NO_CERTIFICATION: + case PGPSignature.CASUAL_CERTIFICATION: + case PGPSignature.POSITIVE_CERTIFICATION: + if (selfCert == null) { + selfCert = zert; + } else if (selfCert.getCreationTime().before(cert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, selfCert); + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_UID_DUP, + new String[] { userId }, indent); + selfCert = zert; + } else { + modified = PGPPublicKey.removeCertification(modified, userId, zert); + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_UID_DUP, + new String[] { userId }, indent); + } + // If there is a revocation certificate, and it's older than this, drop it + if (revocation != null + && revocation.getCreationTime().before(selfCert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, revocation); + revocation = null; + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_OLD, + new String[] { userId }, indent); + } + break; + + case PGPSignature.CERTIFICATION_REVOCATION: + // If this is older than the (latest) self cert, drop it + if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, zert); + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_OLD, + new String[] { userId }, indent); + continue; + } + // first revocation? remember it. + if (revocation == null) { + revocation = zert; + // more revocations? at least one is superfluous, then. + } else if (revocation.getCreationTime().before(cert.getCreationTime())) { + modified = PGPPublicKey.removeCertification(modified, userId, revocation); + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_DUP, + new String[] { userId }, indent); + revocation = zert; + } else { + modified = PGPPublicKey.removeCertification(modified, userId, zert); + removedCerts += 1; + log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_DUP, + new String[] { userId }, indent); + } + break; + + } + + } + } + + // Replace modified key in the keyring + ring = PGPPublicKeyRing.insertPublicKey(ring, modified); + + log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER_SUCCESS, null, indent); + indent -= 1; + + } + + // Process all keys + for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) { + // Don't care about the master key here, that one gets special treatment above + if (key.isMasterKey()) { + continue; + } + log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB, + new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent); + indent += 1; + // A subkey needs exactly one subkey binding certificate, and optionally one revocation + // certificate. + PGPPublicKey modified = key; + PGPSignature selfCert = null, revocation = null; + uids: for (PGPSignature zig : new IterableIterator<PGPSignature>(key.getSignatures())) { + // remove from keyring (for now) + modified = PGPPublicKey.removeCertification(modified, zig); + // add this too, easier than adding it for every single "continue" case + removedCerts += 1; + + WrappedSignature cert = new WrappedSignature(zig); + int type = cert.getSignatureType(); + + // filter out bad key types... + if (cert.getKeyId() != masterKey.getKeyID()) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_KEYID, null, indent); + continue; + } + + if (type != PGPSignature.SUBKEY_BINDING && type != PGPSignature.SUBKEY_REVOCATION) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TYPE, new String[]{ + "0x" + Integer.toString(type, 16) + }, indent); + continue; + } + + if (cert.getCreationTime().after(now)) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TIME, null, indent); + continue; + } + + if (cert.isLocal()) { + // Creation date in the future? No way! + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_LOCAL, null, indent); + continue; + } + + if (type == PGPSignature.SUBKEY_BINDING) { + + // make sure the certificate checks out + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey, key)) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD, null, indent); + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_ERR, null, indent); + continue; + } + + if (zig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) { + int flags = ((KeyFlags) zig.getHashedSubPackets() + .getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags(); + // If this subkey is allowed to sign data, + if ((flags & PGPKeyFlags.CAN_SIGN) == PGPKeyFlags.CAN_SIGN) { + try { + PGPSignatureList list = zig.getUnhashedSubPackets().getEmbeddedSignatures(); + boolean ok = false; + for (int i = 0; i < list.size(); i++) { + WrappedSignature subsig = new WrappedSignature(list.get(i)); + if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { + subsig.init(key); + if (subsig.verifySignature(masterKey, key)) { + ok = true; + } else { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD, null, indent); + continue uids; + } + } + } + if (!ok) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, null, indent); + continue; + } + } catch (Exception e) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, null, indent); + continue; + } + } + } + + // if we already have a cert, and this one is not newer: skip it + if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) { + continue; + } + + selfCert = zig; + // if this is newer than a possibly existing revocation, drop that one + if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) { + revocation = null; + } + + // it must be a revocation, then (we made sure above) + } else { + + // make sure the certificate checks out + try { + cert.init(masterKey); + if (!cert.verifySignature(key)) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, null, indent); + continue; + } + } catch (PgpGeneralException e) { + log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD_ERR, null, indent); + continue; + } + + // if there is no binding (yet), or the revocation is newer than the binding: keep it + if (selfCert == null || selfCert.getCreationTime().before(cert.getCreationTime())) { + revocation = zig; + } + } + } + + // it is not properly bound? error! + if (selfCert == null) { + ring = PGPPublicKeyRing.removePublicKey(ring, modified); + + log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT, + new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent); + indent -= 1; + continue; + } + + // re-add certification + modified = PGPPublicKey.addCertification(modified, selfCert); + removedCerts -= 1; + // add revocation, if any + if (revocation != null) { + modified = PGPPublicKey.addCertification(modified, revocation); + removedCerts -= 1; + } + // replace pubkey in keyring + ring = PGPPublicKeyRing.insertPublicKey(ring, modified); + + log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_SUCCESS, null, indent); + indent -= 1; + } + + if (removedCerts > 0) { + log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_REMOVED, + new String[] { Integer.toString(removedCerts) }, indent); + } else { + log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, null, indent); + } + + return new UncachedKeyRing(ring); + } + + } 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 108c8c8c3..33db7771b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -2,6 +2,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureSubpacketVector; @@ -9,6 +10,7 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProv import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.IterableIterator; +import java.security.SignatureException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -28,8 +30,13 @@ public class UncachedPublicKey { } /** The revocation signature is NOT checked here, so this may be false! */ - public boolean maybeRevoked() { - return mPublicKey.isRevoked(); + public boolean isRevoked() { + for (PGPSignature sig : new IterableIterator<PGPSignature>( + mPublicKey.getSignaturesOfType(isMasterKey() ? PGPSignature.KEY_REVOCATION + : PGPSignature.SUBKEY_REVOCATION))) { + return true; + } + return false; } public Date getCreationTime() { @@ -193,4 +200,5 @@ public class UncachedPublicKey { } }; } + } 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 1b7a5e8ba..be7f960a9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -35,7 +35,7 @@ public class WrappedSignature { final PGPSignature mSig; - protected WrappedSignature(PGPSignature sig) { + WrappedSignature(PGPSignature sig) { mSig = sig; } @@ -88,7 +88,7 @@ public class WrappedSignature { init(key.getPublicKey()); } - protected void init(PGPPublicKey key) throws PgpGeneralException { + void init(PGPPublicKey key) throws PgpGeneralException { try { JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() @@ -125,7 +125,27 @@ public class WrappedSignature { } } - protected boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { + 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); + } + } + + 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); + } + } + + boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException { try { return mSig.verifyCertification(uid, key); } catch (SignatureException e) { @@ -158,4 +178,11 @@ public class WrappedSignature { return new WrappedSignature(signatures.get(0)); } + public boolean isLocal() { + if (!mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPORTABLE)) { + return false; + } + SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.EXPORTABLE); + return p.getData()[0] == 0; + } } 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..102c8e6d0 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.pgp.WrappedPublicKey; +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.SaveKeyringResult; 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<OperationResultParcel.LogEntryParcel> mLog; + private OperationLog mLog; private int mIndent; public ProviderHelper(Context context) { - this(context, new ArrayList<OperationResultParcel.LogEntryParcel>(), 0); + this(context, new OperationLog(), 0); } - public ProviderHelper(Context context, ArrayList<OperationResultParcel.LogEntryParcel> 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); } } @@ -156,36 +172,31 @@ public class ProviderHelper { } } - public Object getUnifiedData(long masterKeyId, String column, int type) - throws NotFoundException { - return getUnifiedData(masterKeyId, new String[]{column}, new int[]{type}).get(column); - } - public HashMap<String, Object> getUnifiedData(long masterKeyId, String[] proj, int[] types) throws NotFoundException { return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); } - private LongSparseArray<UncachedPublicKey> getUncachedMasterKeys(Uri queryUri) { - Cursor cursor = mContentResolver.query(queryUri, - new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA}, - null, null, null); + private LongSparseArray<WrappedPublicKey> getTrustedMasterKeys() { + Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] { + KeyRings.MASTER_KEY_ID, + // we pick from cache only information that is not easily available from keyrings + KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED, + // and of course, ring data + KeyRings.PUBKEY_DATA + }, KeyRings.HAS_ANY_SECRET + " = 1", null, null); - LongSparseArray<UncachedPublicKey> result = - new LongSparseArray<UncachedPublicKey>(cursor.getCount()); + LongSparseArray<WrappedPublicKey> result = + new LongSparseArray<WrappedPublicKey>(cursor.getCount()); try { if (cursor != null && cursor.moveToFirst()) do { long masterKeyId = cursor.getLong(0); - byte[] data = cursor.getBlob(1); - if (data != null) { - try { - result.put(masterKeyId, - UncachedKeyRing.decodeFromData(data).getPublicKey()); - } catch(PgpGeneralException e) { - Log.e(Constants.TAG, "Error parsing keyring, skipping " + masterKeyId, e); - } catch(IOException e) { - Log.e(Constants.TAG, "IO error, skipping keyring" + masterKeyId, e); - } + boolean hasAnySecret = cursor.getInt(1) > 0; + int verified = cursor.getInt(2); + byte[] blob = cursor.getBlob(3); + if (blob != null) { + result.put(masterKeyId, + new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey()); } } while (cursor.moveToNext()); } finally { @@ -236,7 +247,7 @@ public class ProviderHelper { throw new NotFoundException("Secret key not available!"); } return secret - ? new WrappedSecretKeyRing(blob, hasAnySecret, verified) + ? new WrappedSecretKeyRing(blob, true, verified) : new WrappedPublicKeyRing(blob, hasAnySecret, verified); } else { throw new NotFoundException("Key not found!"); @@ -252,18 +263,25 @@ public class ProviderHelper { * Saves PGPPublicKeyRing with its keys and userIds in DB */ @SuppressWarnings("unchecked") - public OperationResultParcel savePublicKeyRing(UncachedKeyRing keyRing) { + public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) { if (keyRing.isSecret()) { log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET); - return new OperationResultParcel(1, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); } - UncachedPublicKey masterKey = keyRing.getPublicKey(); - long masterKeyId = masterKey.getKeyId(); - log(LogLevel.INFO, LogType.MSG_IP_IMPORTING, - new String[]{Long.toString(masterKeyId)}); + // start with ok result + int result = SaveKeyringResult.SAVED_PUBLIC; + + long masterKeyId = keyRing.getMasterKeyId(); + log(LogLevel.START, LogType.MSG_IP, + new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); mIndent += 1; + // Canonicalize this key, to assert a number of assumptions made about it. + keyRing = keyRing.canonicalize(mLog, mIndent); + + UncachedPublicKey masterKey = keyRing.getPublicKey(); + // IF there is a secret key, preserve it! UncachedKeyRing secretRing; try { @@ -273,65 +291,105 @@ public class ProviderHelper { secretRing = null; } - // delete old version of this keyRing, which also deletes all keys and userIds on cascade + ArrayList<ContentProviderOperation> operations; try { - mContentResolver.delete(KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null); - log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK); - } catch (UnsupportedOperationException e) { - Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e); - 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<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); + log(LogLevel.DEBUG, LogType.MSG_IP_PREPARE); + mIndent += 1; - try { + // save all keys and userIds included in keyRing object in database + operations = new ArrayList<ContentProviderOperation>(); 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 SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, 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<UncachedPublicKey>(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<UncachedPublicKey>(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(), e = key.canEncrypt(), s = key.canSign(); + values.put(Keys.CAN_CERTIFY, c); + values.put(Keys.CAN_ENCRYPT, e); + values.put(Keys.CAN_SIGN, s); + values.put(Keys.IS_REVOKED, key.isRevoked()); + if (c) { + if (e) { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CES + : LogType.MSG_IP_SUBKEY_FLAGS_CEX, null); + } else { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CXS + : LogType.MSG_IP_SUBKEY_FLAGS_CXX, null); + } + } else { + if (e) { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XES + : LogType.MSG_IP_SUBKEY_FLAGS_XEX, null); + } else { + log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XXS + : LogType.MSG_IP_SUBKEY_FLAGS_XXX, null); + } + } + + Date creation = key.getCreationTime(); + values.put(Keys.CREATION, creation.getTime() / 1000); + Date expiryDate = key.getExpiryTime(); + if (expiryDate != null) { + values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + if (key.isExpired()) { + log(LogLevel.DEBUG, 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; - log(LogLevel.DEBUG, LogType.MSG_IP_TRUST_RETRIEVE); // get a list of owned secret keys, for verification filtering - LongSparseArray<UncachedPublicKey> trustedKeys = - getUncachedMasterKeys(KeyRingData.buildSecretKeyRingUri()); - // special case: available secret keys verify themselves! - if (secretRing != null) { - trustedKeys.put(secretRing.getMasterKeyId(), secretRing.getPublicKey()); - log(LogLevel.INFO, LogType.MSG_IP_TRUST_USING_SEC, new String[]{ - Integer.toString(trustedKeys.size()) - }); - } else { - log(LogLevel.INFO, LogType.MSG_IP_TRUST_USING, new String[] { - Integer.toString(trustedKeys.size()) - }); - } + LongSparseArray<WrappedPublicKey> trustedKeys = getTrustedMasterKeys(); // classify and order user ids. primary are moved to the front, revoked to the back, // otherwise the order in the keyfile is preserved. - log(LogLevel.DEBUG, LogType.MSG_IP_UID_CLASSIFYING); + log(LogLevel.INFO, LogType.MSG_IP_UID_CLASSIFYING, new String[]{ + Integer.toString(trustedKeys.size()) + }); mIndent += 1; List<UserIdItem> uids = new ArrayList<UserIdItem>(); for (String userId : new IterableIterator<String>( @@ -342,7 +400,7 @@ public class ProviderHelper { int unknownCerts = 0; - log(LogLevel.INFO, LogType.MSG_IP_UID_PROCESSING, new String[] { userId }); + log(LogLevel.INFO, LogType.MSG_IP_UID_PROCESSING, new String[]{ userId }); mIndent += 1; // look through signatures for this specific key for (WrappedSignature cert : new IterableIterator<WrappedSignature>( @@ -351,41 +409,29 @@ public class ProviderHelper { try { // self signature if (certId == masterKeyId) { - cert.init(masterKey); - if (!cert.verifySignature(masterKey, userId)) { - // Bad self certification? That's kinda bad... - log(LogLevel.ERROR, LogType.MSG_IP_UID_SELF_BAD); - return new OperationResultParcel(1, mLog); - } - // if we already have a cert.. - if (item.selfCert != null) { - // ..is this perchance a more recent one? - if (item.selfCert.getCreationTime().before(cert.getCreationTime())) { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_NEWER); - } else { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_IGNORING_OLD); - continue; - } - } else { + // NOTE self-certificates are already verified during canonicalization, + // AND we know there is at most one cert plus at most one revocation + if (!cert.isRevocation()) { + item.selfCert = cert; + item.isPrimary = cert.isPrimaryUserId(); log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_GOOD); + } else { + item.isRevoked = true; + log(LogLevel.DEBUG, LogType.MSG_IP_UID_REVOKED); } - // save certificate as primary self-cert - item.selfCert = cert; - item.isPrimary = cert.isPrimaryUserId(); - item.isRevoked = cert.isRevocation(); - } // verify signatures from known private keys if (trustedKeys.indexOfKey(certId) >= 0) { - UncachedPublicKey trustedKey = trustedKeys.get(certId); + WrappedPublicKey trustedKey = trustedKeys.get(certId); cert.init(trustedKey); if (cert.verifySignature(masterKey, userId)) { item.trustedCerts.add(cert); log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD, new String[] { - PgpKeyHelper.convertKeyIdToHex(trustedKey.getKeyId()) + PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()), + trustedKey.getPrimaryUserId() }); } else { log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD); @@ -400,18 +446,18 @@ public class ProviderHelper { }); } } - mIndent -= 1; if (unknownCerts > 0) { - log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, new String[] { + log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, new String[]{ Integer.toString(unknownCerts) }); } + mIndent -= 1; } mIndent -= 1; - log(LogLevel.INFO, LogType.MSG_IP_UID_INSERT); + log(LogLevel.DEBUG, LogType.MSG_IP_UID_REORDER); // primary before regular before revoked (see UserIdItem.compareTo) // this is a stable sort, so the order of keys is otherwise preserved. Collections.sort(uids); @@ -419,10 +465,9 @@ public class ProviderHelper { for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { UserIdItem item = uids.get(userIdRank); operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); - // no self cert is bad, but allowed by the rfc... if (item.selfCert != null) { - operations.add(buildCertOperations( - masterKeyId, userIdRank, item.selfCert, Certs.VERIFIED_SELF)); + operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, + secretRing != null ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); } // don't bother with trusted certs if the uid is revoked, anyways if (item.isRevoked) { @@ -434,37 +479,55 @@ public class ProviderHelper { } } - log(LogLevel.DEBUG, LogType.MSG_IP_APPLY_BATCH); - mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); + log(LogLevel.DEBUG, LogType.MSG_IP_PREPARE_SUCCESS); + mIndent -= 1; + } catch (IOException e) { log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC); Log.e(Constants.TAG, "IOException during import", e); mIndent -= 1; - return new OperationResultParcel(1, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + } + + try { + // delete old version of this keyRing, which also deletes all keys and userIds on cascade + int deleted = mContentResolver.delete( + KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null); + if (deleted > 0) { + log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK); + result |= SaveKeyringResult.UPDATED; + } else { + log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_FAIL); + } + + log(LogLevel.DEBUG, LogType.MSG_IP_APPLY_BATCH); + mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); + + // Save the saved keyring (if any) + if (secretRing != null) { + log(LogLevel.DEBUG, LogType.MSG_IP_REINSERT_SECRET); + mIndent += 1; + saveSecretKeyRing(secretRing); + result |= SaveKeyringResult.SAVED_SECRET; + mIndent -= 1; + } + + mIndent -= 1; + log(LogLevel.OK, LogType.MSG_IP_SUCCESS); + return new SaveKeyringResult(result, mLog); + } catch (RemoteException e) { log(LogLevel.ERROR, LogType.MSG_IP_FAIL_REMOTE_EX); Log.e(Constants.TAG, "RemoteException during import", e); mIndent -= 1; - return new OperationResultParcel(1, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); } catch (OperationApplicationException e) { log(LogLevel.ERROR, LogType.MSG_IP_FAIL_OP_EX); Log.e(Constants.TAG, "OperationApplicationException during import", e); mIndent -= 1; - return new OperationResultParcel(1, mLog); - } - - // Save the saved keyring (if any) - if (secretRing != null) { - log(LogLevel.DEBUG, LogType.MSG_IP_REINSERT_SECRET); - mIndent += 1; - saveSecretKeyRing(secretRing); - mIndent -= 1; + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); } - log(LogLevel.INFO, LogType.MSG_IP_SUCCESS); - mIndent -= 1; - return new OperationResultParcel(0, mLog); - } private static class UserIdItem implements Comparable<UserIdItem> { @@ -491,16 +554,23 @@ public class ProviderHelper { /** * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring * is already in the database! + * + * TODO allow adding secret keys where no public key exists (ie, consolidate keys) */ - public OperationResultParcel saveSecretKeyRing(UncachedKeyRing keyRing) { + public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing keyRing) { + if (!keyRing.isSecret()) { log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC); - return new OperationResultParcel(1, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); } long masterKeyId = keyRing.getMasterKeyId(); - log(LogLevel.INFO, LogType.MSG_IS_IMPORTING, - new String[]{ Long.toString(masterKeyId) }); + log(LogLevel.START, LogType.MSG_IS, + new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); + mIndent += 1; + + // IF this is successful, it's a secret key + int result = SaveKeyringResult.SAVED_SECRET; // save secret keyring try { @@ -513,7 +583,7 @@ public class ProviderHelper { } catch (IOException e) { Log.e(Constants.TAG, "Failed to encode key!", e); log(LogLevel.ERROR, LogType.MSG_IS_IO_EXCPTION); - return new OperationResultParcel(1, mLog); + return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); } { @@ -556,8 +626,8 @@ public class ProviderHelper { // with has_secret = 0 } - log(LogLevel.INFO, LogType.MSG_IS_SUCCESS); - return new OperationResultParcel(0, mLog); + log(LogLevel.OK, LogType.MSG_IS_SUCCESS); + return new SaveKeyringResult(result, mLog); } @@ -579,37 +649,6 @@ public class ProviderHelper { * 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 - */ - private ContentProviderOperation buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, int verified) throws IOException { ContentValues values = new ContentValues(); 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 27f41e3d2..3ddcdfcf4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -175,14 +175,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; @@ -649,7 +646,10 @@ public class KeychainIntentService extends IntentService List<ParcelableKeyRing> 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/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java new file mode 100644 index 000000000..b5f01ce4d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -0,0 +1,250 @@ +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; + +/** Represent the result of an operation. + * + * This class holds a result and the log of an operation. It can be subclassed + * to include typed additional information specific to the operation. To keep + * the class structure (somewhat) simple, this class contains an exhaustive + * list (ie, enum) of all possible log types, which should in all cases be tied + * to string resource ids. + * + */ +public class OperationResultParcel implements Parcelable { + /** Holds the overall result, the number specifying varying degrees of success. The first bit + * is 0 on overall success, 1 on overall failure. All other bits may be used for more specific + * conditions. */ + final int mResult; + + public static final int RESULT_OK = 0; + public static final int RESULT_ERROR = 1; + + /// 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 = new OperationLog(); + mLog.addAll(source.createTypedArrayList(LogEntryParcel.CREATOR)); + } + + public int getResult() { + return mResult; + } + + public boolean success() { + return (mResult & 1) == 0; + } + + public OperationLog getLog() { + return mLog; + } + + /** One entry in the log. */ + public static class LogEntryParcel implements Parcelable { + 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; + mType = type; + mParameters = parameters; + mIndent = indent; + } + public LogEntryParcel(LogLevel level, LogType type, String[] parameters) { + this(level, type, parameters, 0); + } + + public LogEntryParcel(Parcel source) { + mLevel = LogLevel.values()[source.readInt()]; + mType = LogType.values()[source.readInt()]; + mParameters = source.createStringArray(); + mIndent = source.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLevel.ordinal()); + dest.writeInt(mType.ordinal()); + dest.writeStringArray(mParameters); + dest.writeInt(mIndent); + } + + public static final Creator<LogEntryParcel> CREATOR = new Creator<LogEntryParcel>() { + public LogEntryParcel createFromParcel(final Parcel source) { + return new LogEntryParcel(source); + } + + public LogEntryParcel[] newArray(final int size) { + return new LogEntryParcel[size]; + } + }; + + } + + public static enum LogType { + + // import public + MSG_IP(R.string.msg_ip), + MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch), + MSG_IP_BAD_TYPE_SECRET (R.string.msg_ip_bad_type_secret), + MSG_IP_DELETE_OLD_FAIL (R.string.msg_ip_delete_old_fail), + MSG_IP_DELETE_OLD_OK (R.string.msg_ip_delete_old_ok), + MSG_IP_ENCODE_FAIL (R.string.msg_ip_encode_fail), + MSG_IP_FAIL_IO_EXC (R.string.msg_ip_fail_io_exc), + MSG_IP_FAIL_OP_EX (R.string.msg_ip_fail_op_ex), + MSG_IP_FAIL_REMOTE_EX (R.string.msg_ip_fail_remote_ex), + MSG_IP_INSERT_KEYRING (R.string.msg_ip_insert_keyring), + MSG_IP_INSERT_SUBKEYS (R.string.msg_ip_insert_subkeys), + MSG_IP_PREPARE (R.string.msg_ip_prepare), + MSG_IP_PREPARE_SUCCESS(R.string.msg_ip_prepare_success), + 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_FLAGS_CES (R.string.msg_ip_subkey_flags_ces), + MSG_IP_SUBKEY_FLAGS_CEX (R.string.msg_ip_subkey_flags_cex), + MSG_IP_SUBKEY_FLAGS_CXS (R.string.msg_ip_subkey_flags_cxs), + MSG_IP_SUBKEY_FLAGS_XES (R.string.msg_ip_subkey_flags_xes), + MSG_IP_SUBKEY_FLAGS_CXX (R.string.msg_ip_subkey_flags_cxx), + MSG_IP_SUBKEY_FLAGS_XEX (R.string.msg_ip_subkey_flags_xex), + MSG_IP_SUBKEY_FLAGS_XXS (R.string.msg_ip_subkey_flags_xxs), + MSG_IP_SUBKEY_FLAGS_XXX (R.string.msg_ip_subkey_flags_xxx), + MSG_IP_SUCCESS (R.string.msg_ip_success), + MSG_IP_UID_CERT_BAD (R.string.msg_ip_uid_cert_bad), + MSG_IP_UID_CERT_ERROR (R.string.msg_ip_uid_cert_error), + MSG_IP_UID_CERT_GOOD (R.string.msg_ip_uid_cert_good), + MSG_IP_UID_CERTS_UNKNOWN (R.string.msg_ip_uid_certs_unknown), + MSG_IP_UID_CLASSIFYING (R.string.msg_ip_uid_classifying), + MSG_IP_UID_REORDER(R.string.msg_ip_uid_reorder), + MSG_IP_UID_PROCESSING (R.string.msg_ip_uid_processing), + MSG_IP_UID_REVOKED (R.string.msg_ip_uid_revoked), + MSG_IP_UID_SELF_GOOD (R.string.msg_ip_uid_self_good), + + // import secret + MSG_IS(R.string.msg_is), + MSG_IS_BAD_TYPE_PUBLIC (R.string.msg_is_bad_type_public), + MSG_IS_IMPORTING_SUBKEYS (R.string.msg_is_importing_subkeys), + MSG_IS_IO_EXCPTION (R.string.msg_is_io_excption), + MSG_IS_SUBKEY_NONEXISTENT (R.string.msg_is_subkey_nonexistent), + MSG_IS_SUBKEY_OK (R.string.msg_is_subkey_ok), + MSG_IS_SUBKEY_STRIPPED (R.string.msg_is_subkey_stripped), + MSG_IS_SUCCESS (R.string.msg_is_success), + + // keyring canonicalization + MSG_KC (R.string.msg_kc), + MSG_KC_MASTER (R.string.msg_kc_master), + MSG_KC_MASTER_SUCCESS (R.string.msg_kc_master_success), + MSG_KC_REVOKE_BAD_ERR (R.string.msg_kc_revoke_bad_err), + MSG_KC_REVOKE_BAD_LOCAL (R.string.msg_kc_revoke_bad_local), + MSG_KC_REVOKE_BAD_TIME (R.string.msg_kc_revoke_bad_time), + MSG_KC_REVOKE_BAD_TYPE (R.string.msg_kc_revoke_bad_type), + MSG_KC_REVOKE_BAD (R.string.msg_kc_revoke_bad), + MSG_KC_REVOKE_DUP (R.string.msg_kc_revoke_dup), + MSG_KC_SUB (R.string.msg_kc_sub), + MSG_KC_SUB_BAD(R.string.msg_kc_sub_bad), + MSG_KC_SUB_BAD_ERR(R.string.msg_kc_sub_bad_err), + MSG_KC_SUB_BAD_LOCAL(R.string.msg_kc_sub_bad_local), + MSG_KC_SUB_BAD_KEYID(R.string.msg_kc_sub_bad_keyid), + MSG_KC_SUB_BAD_TIME(R.string.msg_kc_sub_bad_time), + MSG_KC_SUB_BAD_TYPE(R.string.msg_kc_sub_bad_type), + MSG_KC_SUB_PRIMARY_BAD(R.string.msg_kc_sub_primary_bad), + MSG_KC_SUB_PRIMARY_BAD_ERR(R.string.msg_kc_sub_primary_bad_err), + MSG_KC_SUB_PRIMARY_NONE(R.string.msg_kc_sub_primary_none), + MSG_KC_SUB_NO_CERT(R.string.msg_kc_sub_no_cert), + MSG_KC_SUB_REVOKE_BAD_ERR (R.string.msg_kc_sub_revoke_bad_err), + MSG_KC_SUB_REVOKE_BAD (R.string.msg_kc_sub_revoke_bad), + MSG_KC_SUB_REVOKE_DUP (R.string.msg_kc_sub_revoke_dup), + MSG_KC_SUB_SUCCESS (R.string.msg_kc_sub_success), + MSG_KC_SUCCESS_REMOVED (R.string.msg_kc_success_removed), + MSG_KC_SUCCESS (R.string.msg_kc_success), + MSG_KC_UID_BAD_ERR (R.string.msg_kc_uid_bad_err), + MSG_KC_UID_BAD_LOCAL (R.string.msg_kc_uid_bad_local), + 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_DUP (R.string.msg_kc_uid_dup), + MSG_KC_UID_REVOKE_DUP (R.string.msg_kc_uid_revoke_dup), + MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old), + ; + + private final int mMsgId; + LogType(int msgId) { + mMsgId = msgId; + } + public int getMsgId() { + return mMsgId; + } + } + + /** Enumeration of possible log levels. */ + public static enum LogLevel { + DEBUG, + INFO, + WARN, + ERROR, // should occur once at the end of a failed operation + START, // should occur once at the start of each independent operation + OK, // should occur once at the end of a successful operation + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResult); + dest.writeTypedList(mLog); + } + + public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() { + public OperationResultParcel createFromParcel(final Parcel source) { + return new OperationResultParcel(source); + } + + public OperationResultParcel[] newArray(final int size) { + return new OperationResultParcel[size]; + } + }; + + public static class OperationLog extends ArrayList<LogEntryParcel> { + + /// 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() { + for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(iterator())) { + if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) { + 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..6c44b01f1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResults.java @@ -0,0 +1,92 @@ +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; + + // At least one new key + public static final int RESULT_OK_NEWKEYS = 2; + // At least one updated key + public static final int RESULT_OK_UPDATED = 4; + // At least one key failed (might still be an overall success) + public static final int RESULT_WITH_ERRORS = 8; + // There are warnings in the log + public static final int RESULT_WITH_WARNINGS = 16; + + // No keys to import... + 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; + } + + 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<ImportResult> CREATOR = new Creator<ImportResult>() { + public ImportResult createFromParcel(final Parcel source) { + return new ImportResult(source); + } + + public ImportResult[] newArray(final int size) { + return new ImportResult[size]; + } + }; + + } + + public static class SaveKeyringResult extends OperationResultParcel { + + public SaveKeyringResult(int result, OperationLog log) { + super(result, log); + } + + // Some old key was updated + public static final int UPDATED = 2; + + // Public key was saved + public static final int SAVED_PUBLIC = 8; + // Secret key was saved (not exclusive with public!) + public static final int SAVED_SECRET = 16; + + public boolean updated() { + return (mResult & UPDATED) == UPDATED; + } + + } + +} 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 48602aaa1..f389726ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -38,7 +38,10 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; -import com.devspark.appmsg.AppMsg; +import com.github.johnpersano.supertoasts.SuperCardToast; +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; @@ -47,7 +50,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.ui.dialog.BadImportKeyDialogFragment; +import org.sufficientlysecure.keychain.service.OperationResults.ImportResult; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; @@ -135,6 +138,7 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O } handleActions(savedInstanceState, getIntent()); + } protected void handleActions(Bundle savedInstanceState, Intent intent) { @@ -331,8 +335,11 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O public void loadFromFingerprint(Bundle savedInstanceState, String fingerprint) { if (fingerprint == null || fingerprint.length() < 40) { - AppMsg.makeText(this, R.string.import_qr_code_too_short_fingerprint, - AppMsg.STYLE_ALERT).show(); + SuperCardToast toast = SuperCardToast.create(this, + getString(R.string.import_qr_code_too_short_fingerprint), + SuperToast.Duration.LONG); + toast.setBackground(SuperToast.Background.RED); + toast.show(); return; } @@ -368,39 +375,93 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { // get returned data bundle Bundle returnData = message.getData(); + final ImportResult result = + returnData.<ImportResult>getParcelable(KeychainIntentService.RESULT); + + int resultType = result.getResult(); + + String str; + int duration, color; + + // Not an overall failure + if ((resultType & ImportResult.RESULT_ERROR) == 0) { + String withWarnings; + + // Any warnings? + if ((resultType & ImportResult.RESULT_WITH_WARNINGS) > 0) { + duration = 0; + color = Style.ORANGE; + withWarnings = getResources().getString(R.string.import_with_warnings); + } else { + duration = SuperToast.Duration.LONG; + color = Style.GREEN; + withWarnings = ""; + } + + // New and updated keys + if (result.isOkBoth()) { + str = getResources().getQuantityString( + R.plurals.import_keys_added_and_updated_1, result.mNewKeys, result.mNewKeys); + str += getResources().getQuantityString( + R.plurals.import_keys_added_and_updated_2, result.mUpdatedKeys, result.mUpdatedKeys, withWarnings); + } else if (result.isOkUpdated()) { + str = getResources().getQuantityString( + R.plurals.import_keys_updated, result.mUpdatedKeys, result.mUpdatedKeys, withWarnings); + } else if (result.isOkNew()) { + str = getResources().getQuantityString( + R.plurals.import_keys_added, result.mNewKeys, result.mNewKeys, withWarnings); + } else { + duration = 0; + color = Style.RED; + str = "internal error"; + } - int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED); - int updated = returnData - .getInt(KeychainIntentService.RESULT_IMPORT_UPDATED); - int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD); - String toastMessage; - if (added > 0 && updated > 0) { - String addedStr = getResources().getQuantityString( - R.plurals.keys_added_and_updated_1, added, added); - String updatedStr = getResources().getQuantityString( - R.plurals.keys_added_and_updated_2, updated, updated); - toastMessage = addedStr + updatedStr; - } else if (added > 0) { - toastMessage = getResources().getQuantityString(R.plurals.keys_added, - added, added); - } else if (updated > 0) { - toastMessage = getResources().getQuantityString(R.plurals.keys_updated, - updated, updated); } else { - toastMessage = getString(R.string.no_keys_added_or_updated); + duration = 0; + color = Style.RED; + if (result.isFailNothing()) { + str = getString(R.string.import_error_nothing); + } else { + str = getString(R.string.import_error); + } } - AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO) - .show(); + + SuperCardToast toast = new SuperCardToast(ImportKeysActivity.this, + SuperToast.Type.BUTTON, Style.getStyle(color, SuperToast.Animations.POPUP)); + toast.setText(str); + toast.setDuration(duration); + toast.setIndeterminate(duration == 0); + toast.setSwipeToDismiss(true); + toast.setButtonIcon(R.drawable.ic_action_view_as_list, + getResources().getString(R.string.import_view_log)); + toast.setButtonTextColor(getResources().getColor(R.color.black)); + toast.setTextColor(getResources().getColor(R.color.black)); + toast.setOnClickWrapper(new OnClickWrapper("supercardtoast", + new SuperToast.OnClickListener() { + @Override + public void onClick(View view, Parcelable token) { + Intent intent = new Intent( + ImportKeysActivity.this, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); + startActivity(intent); + } + })); + toast.show(); + + /* if (bad > 0) { BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad); badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog"); } + */ + /* if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { ImportKeysActivity.this.setResult(Activity.RESULT_OK, mPendingIntentData); finish(); } + */ } } }; @@ -483,7 +544,11 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O startService(intent); } else { - AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show(); + SuperCardToast toast = SuperCardToast.create(this, + getString(R.string.error_nothing_import), + SuperToast.Duration.LONG); + toast.setBackground(SuperToast.Background.RED); + toast.show(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java new file mode 100644 index 000000000..a0d449195 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java @@ -0,0 +1,21 @@ +package org.sufficientlysecure.keychain.ui; + +import android.os.Bundle; +import android.support.v4.view.GestureDetectorCompat; +import android.support.v7.app.ActionBarActivity; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; + +import org.sufficientlysecure.keychain.R; + +public class LogDisplayActivity extends ActionBarActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.log_display_activity); + } + +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java new file mode 100644 index 000000000..496e98c18 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -0,0 +1,175 @@ +package org.sufficientlysecure.keychain.ui; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.support.v4.app.ListFragment; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.OperationResultParcel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogEntryParcel; +import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; + +public class LogDisplayFragment extends ListFragment implements OnTouchListener { + + HashMap<LogLevel,LogAdapter> mAdapters = new HashMap<LogLevel, LogAdapter>(); + LogAdapter mAdapter; + LogLevel mLevel = LogLevel.DEBUG; + + OperationResultParcel mResult; + + GestureDetector mDetector; + + public static final String EXTRA_RESULT = "log"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Intent intent = getActivity().getIntent(); + if (intent.getExtras() == null || !intent.getExtras().containsKey(EXTRA_RESULT)) { + getActivity().finish(); + return; + } + + mResult = intent.<OperationResultParcel>getParcelableExtra(EXTRA_RESULT); + if (mResult == null) { + getActivity().finish(); + return; + } + + mAdapter = new LogAdapter(getActivity(), mResult.getLog(), LogLevel.DEBUG); + mAdapters.put(LogLevel.DEBUG, mAdapter); + setListAdapter(mAdapter); + + mDetector = new GestureDetector(getActivity(), new SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float vx, float vy) { + Log.d(Constants.TAG, "x: " + vx + ", y: " + vy); + if (vx < -2000) { + decreaseLogLevel(); + } else if (vx > 2000) { + increaseLogLevel(); + } + return true; + } + }); + + } + + public void decreaseLogLevel() { + switch (mLevel) { + case DEBUG: mLevel = LogLevel.INFO; break; + case INFO: mLevel = LogLevel.WARN; break; + } + refreshLevel(); + } + + public void increaseLogLevel() { + switch (mLevel) { + case INFO: mLevel = LogLevel.DEBUG; break; + case WARN: mLevel = LogLevel.INFO; break; + } + refreshLevel(); + } + + private void refreshLevel() { + /* TODO not sure if this is a good idea + if (!mAdapters.containsKey(mLevel)) { + mAdapters.put(mLevel, new LogAdapter(getActivity(), mResult.getLog(), mLevel)); + } + mAdapter = mAdapters.get(mLevel); + setListAdapter(mAdapter); + */ + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getListView().setDividerHeight(0); + getListView().setOnTouchListener(this); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + mDetector.onTouchEvent(event); + return false; + } + + private class LogAdapter extends ArrayAdapter<LogEntryParcel> { + + private LayoutInflater mInflater; + private int dipFactor; + + public LogAdapter(Context context, ArrayList<LogEntryParcel> log, LogLevel level) { + super(context, R.layout.log_display_item); + mInflater = LayoutInflater.from(getContext()); + dipFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + (float) 8, getResources().getDisplayMetrics()); + // we can't use addAll for a LogLevel.DEBUG shortcut here, unfortunately :( + for (LogEntryParcel e : log) { + if (e.mLevel.ordinal() >= level.ordinal()) { + add(e); + } + } + notifyDataSetChanged(); + } + + private class ItemHolder { + final TextView mText; + final ImageView mImg; + public ItemHolder(TextView text, ImageView image) { + mText = text; + mImg = image; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + LogEntryParcel entry = getItem(position); + ItemHolder ih; + if (convertView == null) { + convertView = mInflater.inflate(R.layout.log_display_item, parent, false); + ih = new ItemHolder( + (TextView) convertView.findViewById(R.id.log_text), + (ImageView) convertView.findViewById(R.id.log_img) + ); + convertView.setTag(ih); + } else { + ih = (ItemHolder) convertView.getTag(); + } + + ih.mText.setText(getResources().getString(entry.mType.getMsgId(), (Object[]) entry.mParameters)); + ih.mText.setTextColor(entry.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); + convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); + switch (entry.mLevel) { + case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break; + case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break; + case WARN: ih.mImg.setBackgroundColor(Color.YELLOW); break; + case ERROR: ih.mImg.setBackgroundColor(Color.RED); break; + case START: ih.mImg.setBackgroundColor(Color.GREEN); break; + case OK: ih.mImg.setBackgroundColor(Color.GREEN); break; + } + + return convertView; + } + + } +} diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..86da228e9 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..ccb4c7d7b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..b9c93c8c2 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_view_as_list.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_view_as_list.png Binary files differnew file mode 100644 index 000000000..460041640 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_view_as_list.png diff --git a/OpenKeychain/src/main/res/layout/import_keys_activity.xml b/OpenKeychain/src/main/res/layout/import_keys_activity.xml index 2a332823e..0486b6bd6 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_activity.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_activity.xml @@ -1,22 +1,37 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content_frame" android:layout_marginLeft="@dimen/drawer_content_padding" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="fill_parent" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/card_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> <FrameLayout android:id="@+id/import_navigation_fragment" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentTop="true" android:orientation="vertical" /> + <FrameLayout + android:id="@+id/import_keys_list_container" + android:layout_width="match_parent" + android:layout_height="0dp" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:layout_weight="0.9" /> + <LinearLayout android:id="@+id/import_footer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp"> @@ -43,16 +58,4 @@ style="@style/SelectableItem" /> </LinearLayout> - - <FrameLayout - android:id="@+id/import_keys_list_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_above="@+id/import_footer" - android:layout_alignParentLeft="true" - android:layout_below="@+id/import_navigation_fragment" - android:orientation="vertical" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" /> -</RelativeLayout>
\ No newline at end of file +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_activity.xml b/OpenKeychain/src/main/res/layout/log_display_activity.xml new file mode 100644 index 000000000..591e2650c --- /dev/null +++ b/OpenKeychain/src/main/res/layout/log_display_activity.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <fragment + android:id="@+id/list" + android:name="org.sufficientlysecure.keychain.ui.LogDisplayFragment" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="0.9" + android:layout_marginRight="8dp" + android:layout_marginLeft="8dp" /> + + <LinearLayout + android:id="@+id/import_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <TextView + android:id="@+id/import_import" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:layout_marginBottom="8dp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="Close" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:clickable="true" + style="@style/SelectableItem" /> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_fragment.xml b/OpenKeychain/src/main/res/layout/log_display_fragment.xml new file mode 100644 index 000000000..442e72d09 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/log_display_fragment.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ListView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/log_text" /> +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_item.xml b/OpenKeychain/src/main/res/layout/log_display_item.xml new file mode 100644 index 000000000..35489afed --- /dev/null +++ b/OpenKeychain/src/main/res/layout/log_display_item.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:id="@+id/log_img" + android:minWidth="10dp" + android:background="@color/bg_gray" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Log Entry Text" + android:id="@+id/log_text" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" + android:layout_marginLeft="8dp" /> +</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index e2dfa196b..3485846ad 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Es wurde eine leere Identität hinzugefügt. Wirklich fortfahren?</string> <string name="public_key_deletetion_confirmation">Soll der öffentliche Schlüssel \'%s\' wirklich gelöscht werden?\nDies kann nicht rückgängig gemacht werden! </string> <string name="also_export_secret_keys">Private Schlüssel auch exportieren</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d Schlüssel erfolgreich hinzugefügt</item> <item quantity="other">%d Schlüssel erfolgreich hinzugefügt</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">und %d Schlüssel erfolgreich aktualisiert.</item> <item quantity="other">und %d Schlüssel erfolgreich aktualisiert.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d Schlüssel erfolgreich hinzugefügt.</item> <item quantity="other">%d Schlüssel erfolgreich hinzugefügt.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d Schlüssel erfolgreich aktualisiert.</item> <item quantity="other">%d Schlüssel erfolgreich aktualisiert.</item> </plurals> - <string name="no_keys_added_or_updated">Keine Schlüssel hinzugefügt oder aktualisiert.</string> + <string name="import_error_nothing">Keine Schlüssel hinzugefügt oder aktualisiert.</string> <string name="key_exported">1 Schlüssel erfolgreich exportiert.</string> <string name="keys_exported">%d Schlüssel erfolgreich exportiert.</string> <string name="no_keys_exported">Keine Schlüssel exportiert.</string> diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml index 45d3d565b..f1e4e347d 100644 --- a/OpenKeychain/src/main/res/values-es/strings.xml +++ b/OpenKeychain/src/main/res/values-es/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Ha añadido una identidad vacía, ¿está seguro de que quiere continuar?</string> <string name="public_key_deletetion_confirmation">¿De veras quiere borrar la clave pública \'%s\'?\n¡No puede deshacer esto!</string> <string name="also_export_secret_keys">¿Exportar también las claves secretas?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d clave añadida satisfactoriamente</item> <item quantity="other">%d claves añadidas satisfactoriamente</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">y actualizada %d clave.</item> <item quantity="other">y actualizadas %d claves.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d clave añadida satisfactoriamente.</item> <item quantity="other">%d claves añadidas satisfactoriamente.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d clave actualizada satisfactoriamente.</item> <item quantity="other">%d claves actualizadas satisfactoriamente.</item> </plurals> - <string name="no_keys_added_or_updated">No se han añadido o actualizado claves.</string> + <string name="import_error_nothing">No se han añadido o actualizado claves.</string> <string name="key_exported">Se ha exportado 1 clave satisfactoriamente.</string> <string name="keys_exported">%d claves exportadas satisfactoriamente.</string> <string name="no_keys_exported">No se han exportado claves.</string> diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml index f49127b6f..55a85fb9b 100644 --- a/OpenKeychain/src/main/res/values-fr/strings.xml +++ b/OpenKeychain/src/main/res/values-fr/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Vous avez ajouté une identité vide, êtes-vous certain de vouloir continuer ?</string> <string name="public_key_deletetion_confirmation">Voulez-vous vraiment supprimer la clef publique %s ?\nCeci est irréversible !</string> <string name="also_export_secret_keys">Exporter aussi les clefs secrètes ?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d clef ajoutée avec succès</item> <item quantity="other">%d clefs ajoutées avec succès</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">et %d clef mise à jour.</item> <item quantity="other">et %d clefs mises à jour.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d clef ajoutée avec succès.</item> <item quantity="other">%d clefs ajoutées avec succès.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d clef mise à jour avec succès.</item> <item quantity="other">%d clefs mises à jour avec succès.</item> </plurals> - <string name="no_keys_added_or_updated">Aucune clef ajoutée ou mise à jour.</string> + <string name="import_error_nothing">Aucune clef ajoutée ou mise à jour.</string> <string name="key_exported">1 clef exportée avec succès.</string> <string name="keys_exported">%d clefs exportées avec succès.</string> <string name="no_keys_exported">Aucune clef exportée.</string> diff --git a/OpenKeychain/src/main/res/values-it/strings.xml b/OpenKeychain/src/main/res/values-it/strings.xml index eae4dd4af..300627fa7 100644 --- a/OpenKeychain/src/main/res/values-it/strings.xml +++ b/OpenKeychain/src/main/res/values-it/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">Hai aggiunto una identità vuota, sei sicuro di voler continuare?</string> <string name="public_key_deletetion_confirmation">Vuoi veramente eliminare la chiave pubblica \'%s\'?\nNon potrai annullare!</string> <string name="also_export_secret_keys">Esportare anche le chiavi segrete?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">%d chiave aggiunta correttamente</item> <item quantity="other">%d chiavi aggiunte correttamente</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">e %d chiave aggiornata.</item> <item quantity="other">e %d chiavi aggiornate.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">%d chiave aggiunta correttamente.</item> <item quantity="other">%d chiavi aggiunte correttamente.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">%d chiave aggiornata correttamente.</item> <item quantity="other">%d chiavi aggiornate correttamente.</item> </plurals> - <string name="no_keys_added_or_updated">Nessuna chiave aggiunta o aggiornata.</string> + <string name="import_error_nothing">Nessuna chiave aggiunta o aggiornata.</string> <string name="key_exported">1 chiave esportata correttamente.</string> <string name="keys_exported">%d chiavi esportate correttamente.</string> <string name="no_keys_exported">Nessuna chiave esportata.</string> diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml index c40e9dbdc..63fef2af2 100644 --- a/OpenKeychain/src/main/res/values-ja/strings.xml +++ b/OpenKeychain/src/main/res/values-ja/strings.xml @@ -203,19 +203,19 @@ <string name="ask_empty_id_ok">あなたは空のユーザIDを追加しました、このまま続けますか?</string> <string name="public_key_deletetion_confirmation">公開鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません!</string> <string name="also_export_secret_keys">秘密鍵もエクスポートしますか?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="other">%d の鍵を追加しました</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="other">そして %d の鍵をアップロードしました。</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="other">%d の鍵を追加しました。</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="other">%d の鍵をアップロードしました。</item> </plurals> - <string name="no_keys_added_or_updated">鍵の追加もしくは更新はありませんでした。</string> + <string name="import_error_nothing">鍵の追加もしくは更新はありませんでした。</string> <string name="key_exported">1つの鍵をエクスポートしました。</string> <string name="keys_exported">%d の鍵をエクスポートしました。</string> <string name="no_keys_exported">鍵をエクスポートしていません。</string> diff --git a/OpenKeychain/src/main/res/values-nl/strings.xml b/OpenKeychain/src/main/res/values-nl/strings.xml index d35d83517..f75d7a166 100644 --- a/OpenKeychain/src/main/res/values-nl/strings.xml +++ b/OpenKeychain/src/main/res/values-nl/strings.xml @@ -206,23 +206,23 @@ <string name="ask_empty_id_ok">U heeft een lege identiteit toegevoegd, weet u zeker dat u wilt doorgaan?</string> <string name="public_key_deletetion_confirmation">Wilt u echt de publieke sleutel \'%s\' verwijderen?\nDit kunt u niet ongedaan maken!</string> <string name="also_export_secret_keys">Ook geheime sleutels exporteren?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Succesvol %d sleutel toegevoegd</item> <item quantity="other">Succesvol %d sleutels toegevoegd</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">en %d sleutel bijgewerkt.</item> <item quantity="other">en %d sleutels bijgewerkt.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">Succesvol %d sleutel toegevoegd.</item> <item quantity="other">Succesvol %d sleutels toegevoegd.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">Succesvol %d sleutel bijgewerkt.</item> <item quantity="other">Succesvol %d sleutels bijgewerkt.</item> </plurals> - <string name="no_keys_added_or_updated">Geen sleutels toegevoegd of bijgewerkt.</string> + <string name="import_error_nothing">Geen sleutels toegevoegd of bijgewerkt.</string> <string name="key_exported">1 sleutel succesvol geëxporteerd.</string> <string name="keys_exported">Succesvol %d sleutels geëxporteerd.</string> <string name="no_keys_exported">Geen sleutels geëxporteerd.</string> diff --git a/OpenKeychain/src/main/res/values-pl/strings.xml b/OpenKeychain/src/main/res/values-pl/strings.xml index d1b7de393..851e77c3a 100644 --- a/OpenKeychain/src/main/res/values-pl/strings.xml +++ b/OpenKeychain/src/main/res/values-pl/strings.xml @@ -191,27 +191,27 @@ <string name="ask_save_changed_key">Zostały dokonane zmiany w pęku kluczy, czy chcesz je zachować?</string> <string name="public_key_deletetion_confirmation">Czy na pewno chcesz usunąć klucz publiczny \'%s\'?\nNie można cofnąć tej operacji!</string> <string name="also_export_secret_keys">Czy wyeksportować również klucze prywatne?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Pomyślnie dodano %d klucz</item> <item quantity="few">Pomyślnie dodano %d kluczy</item> <item quantity="other">Pomyślnie dodano %d kluczy</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">i zaktualizowano %d klucz.</item> <item quantity="few">i zaktualizowano %d kluczy.</item> <item quantity="other">i zaktualizowano %d kluczy.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">Pomyślnie dodano %d klucz.</item> <item quantity="few">Pomyślnie dodano %d kluczy.</item> <item quantity="other">Pomyślnie dodano %d kluczy.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">Pomyślnie zaktualizowano %d klucz.</item> <item quantity="few">Pomyślnie zaktualizowano %d kluczy.</item> <item quantity="other">Pomyślnie zaktualizowano %d kluczy.</item> </plurals> - <string name="no_keys_added_or_updated">Nie dodano ani zaktualizowano żadnych kluczy.</string> + <string name="import_error_nothing">Nie dodano ani zaktualizowano żadnych kluczy.</string> <string name="key_exported">Pomyślnie wyeksportowano 1 klucz.</string> <string name="keys_exported">Pomyślnie wyeksportowano %d kluczy.</string> <string name="no_keys_exported">Nie wyeksportowano żadnych kluczy.</string> diff --git a/OpenKeychain/src/main/res/values-ru/strings.xml b/OpenKeychain/src/main/res/values-ru/strings.xml index 0becea0bc..b108324d1 100644 --- a/OpenKeychain/src/main/res/values-ru/strings.xml +++ b/OpenKeychain/src/main/res/values-ru/strings.xml @@ -206,27 +206,27 @@ <string name="ask_empty_id_ok">Вы добавили пустой идентификатор. Вы уверены, что хотите продолжить?</string> <string name="public_key_deletetion_confirmation">Вы правда хотите удалить публичный ключ \'%s\'?\nЭто действие нельзя отменить!</string> <string name="also_export_secret_keys">Экспортировать секретные ключи?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Успешно добавлено %d ключ</item> <item quantity="few">Успешно добавлено %d ключей</item> <item quantity="other">Успешно добавлено %d ключей</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">и обновлен %d ключ.</item> <item quantity="few">и обновлено %d ключей.</item> <item quantity="other">и обновлено %d ключей.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">Добавлен %d ключ</item> <item quantity="few">Добавлено %d ключей</item> <item quantity="other">Добавлено %d ключей</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">Обновлен %d ключ.</item> <item quantity="few">Обновлено %d ключей.</item> <item quantity="other">Обновлено %d ключей.</item> </plurals> - <string name="no_keys_added_or_updated">Нет обновленных или добавленных ключей</string> + <string name="import_error_nothing">Нет обновленных или добавленных ключей</string> <string name="key_exported">Успешный экспорт 1 ключа.</string> <string name="keys_exported">Экспортировано %d ключей.</string> <string name="no_keys_exported">Ключи не были экспортированы.</string> diff --git a/OpenKeychain/src/main/res/values-sl/strings.xml b/OpenKeychain/src/main/res/values-sl/strings.xml index 8b12cdebe..0fe44725b 100644 --- a/OpenKeychain/src/main/res/values-sl/strings.xml +++ b/OpenKeychain/src/main/res/values-sl/strings.xml @@ -212,31 +212,31 @@ <string name="ask_empty_id_ok">Dodali ste prazno identiteto, ali res želite nadaljevati?</string> <string name="public_key_deletetion_confirmation">Ali res želite izbrisati javni ključ \'%s\'?\nTega koraka ne boste mogli preklicati!</string> <string name="also_export_secret_keys">Želite izvoziti tudi zasebne ključe?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Uspešno dodan %d ključ</item> <item quantity="two">Uspešno dodana %d ključa</item> <item quantity="few">Uspešno dodani %d ključi</item> <item quantity="other">Uspešno dodanih %d ključev</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">in posodbljen %d.</item> <item quantity="two">in posodobljena %d.</item> <item quantity="few">in posodobljeni %d.</item> <item quantity="other">in posodobljenih %d.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">Uspešno dodan %d ključ.</item> <item quantity="two">Uspešno dodana %d ključa.</item> <item quantity="few">Uspešno dodani %d ključi.</item> <item quantity="other">Uspešno dodanih %d ključev.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">Uspešno posodobljen %d ključ.</item> <item quantity="two">Uspešno posodobljena %d ključa.</item> <item quantity="few">Uspešno posodobljeni %d ključi.</item> <item quantity="other">Uspešno posodobljenih %d ključev.</item> </plurals> - <string name="no_keys_added_or_updated">Noben ključ ni bil dodan ali posodobljen.</string> + <string name="import_error_nothing">Noben ključ ni bil dodan ali posodobljen.</string> <string name="key_exported">Uspešno izvožen 1 ključ.</string> <string name="keys_exported">Uspešno izvoženih ključev: %d</string> <string name="no_keys_exported">Noben ključ ni bil izvožen.</string> diff --git a/OpenKeychain/src/main/res/values-uk/strings.xml b/OpenKeychain/src/main/res/values-uk/strings.xml index b27b6ffd3..2951d13f8 100644 --- a/OpenKeychain/src/main/res/values-uk/strings.xml +++ b/OpenKeychain/src/main/res/values-uk/strings.xml @@ -209,27 +209,27 @@ <string name="ask_empty_id_ok">Ви вже додали порожню сутність. Ви справді хочете продовжити?</string> <string name="public_key_deletetion_confirmation">Ви справді хочете вилучити відкритий ключ \'%s\'?\nВи не зможете це відмінити!</string> <string name="also_export_secret_keys">Також експортувати секретні ключі?</string> - <plurals name="keys_added_and_updated_1"> + <plurals name="import_keys_added_and_updated_1"> <item quantity="one">Успішно додано %d ключ</item> <item quantity="few">Успішно додано %d ключі</item> <item quantity="other">Успішно додано %d ключів</item> </plurals> - <plurals name="keys_added_and_updated_2"> + <plurals name="import_keys_added_and_updated_2"> <item quantity="one">і оновлено %d ключ.</item> <item quantity="few">і оновлено %d ключі.</item> <item quantity="other">і оновлено %d ключів.</item> </plurals> - <plurals name="keys_added"> + <plurals name="import_keys_added"> <item quantity="one">Успішно додано %d ключ.</item> <item quantity="few">Успішно додано %d ключі.</item> <item quantity="other">Успішно додано %d ключів.</item> </plurals> - <plurals name="keys_updated"> + <plurals name="import_keys_updated"> <item quantity="one">Успішно оновлено %d ключ.</item> <item quantity="few">Успішно оновлено %d ключі.</item> <item quantity="other">Успішно оновлено %d ключів.</item> </plurals> - <string name="no_keys_added_or_updated">Жодного ключа не додано та не оновлено.</string> + <string name="import_error_nothing">Жодного ключа не додано та не оновлено.</string> <string name="key_exported">Успішно експортовано 1 ключ.</string> <string name="keys_exported">Успішно експортовано %d ключів.</string> <string name="no_keys_exported">Жодного ключа не експортовано.</string> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 38f0519ac..5a2c38419 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -28,6 +28,7 @@ <string name="title_certify_key">Certify Identities</string> <string name="title_key_details">Key Details</string> <string name="title_help">Help</string> + <string name="title_log_display">Log</string> <!-- section --> <string name="section_user_ids">Identities</string> @@ -221,24 +222,6 @@ <string name="public_key_deletetion_confirmation">Do you really want to delete the public key \'%s\'?\nYou can\'t undo this!</string> <string name="also_export_secret_keys">Also export secret keys?</string> - <plurals name="keys_added_and_updated_1"> - <item quantity="one">Successfully added %d key</item> - <item quantity="other">Successfully added %d keys</item> - </plurals> - <plurals name="keys_added_and_updated_2"> - <item quantity="one"> and updated %d key.</item> - <item quantity="other"> and updated %d keys.</item> - </plurals> - <plurals name="keys_added"> - <item quantity="one">Successfully added %d key.</item> - <item quantity="other">Successfully added %d keys.</item> - </plurals> - <plurals name="keys_updated"> - <item quantity="one">Successfully updated %d key.</item> - <item quantity="other">Successfully updated %d keys.</item> - </plurals> - - <string name="no_keys_added_or_updated">No keys added or updated.</string> <string name="key_exported">Successfully exported 1 key.</string> <string name="keys_exported">Successfully exported %d keys.</string> <string name="no_keys_exported">No keys exported.</string> @@ -407,6 +390,28 @@ <string name="import_clipboard_button">Get key from clipboard</string> <string name="import_keybase_button">Get key from Keybase.io</string> + <!-- Import result toast --> + <plurals name="import_keys_added_and_updated_1"> + <item quantity="one">Successfully added %1$d key</item> + <item quantity="other">Successfully added %1$d keys</item> + </plurals> + <plurals name="import_keys_added_and_updated_2"> + <item quantity="one"> and updated %1$d key%2$s.</item> + <item quantity="other"> and updated %1$d keys%2$s.</item> + </plurals> + <plurals name="import_keys_added"> + <item quantity="one">Successfully added %1$d key%2$s.</item> + <item quantity="other">Successfully added %1$d keys%2$s.</item> + </plurals> + <plurals name="import_keys_updated"> + <item quantity="one">Successfully updated %1$d key%2$s.</item> + <item quantity="other">Successfully updated %1$d keys%2$s.</item> + </plurals> + <string name="import_view_log">View Log</string> + <string name="import_error_nothing">Nothing to import.</string> + <string name="import_error">Error importing keys!</string> + <string name="import_with_warnings">, with warnings</string> + <!-- Intent labels --> <string name="intent_decrypt_file">Decrypt File with OpenKeychain</string> <string name="intent_import_key">Import Key with OpenKeychain</string> @@ -500,6 +505,91 @@ <string name="cert_verify_error">error!</string> <string name="cert_verify_unavailable">key unavailable</string> + <!-- Import Public log entries --> + <string name="msg_ip_apply_batch">Applying insert batch operation.</string> + <string name="msg_ip_bad_type_secret">Tried to import secret keyring as public. This is a bug, please file a report!</string> + <string name="msg_ip_delete_old_fail">No old key deleted (creating a new one?)</string> + <string name="msg_ip_delete_old_ok">Deleted old key from database</string> + <string name="msg_ip_encode_fail">Operation failed due to encoding error</string> + <string name="msg_ip_fail_io_exc">Operation failed due to i/o error</string> + <string name="msg_ip_fail_op_ex">Operation failed due to database error</string> + <string name="msg_ip_fail_remote_ex">Operation failed due to internal error</string> + <string name="msg_ip">Importing public keyring %s</string> + <string name="msg_ip_insert_keyring">Encoding keyring data</string> + <string name="msg_ip_insert_subkeys">Evaluating subkeys</string> + <string name="msg_ip_prepare">Preparing database operations</string> + <string name="msg_ip_prepare_success">OK</string> + <string name="msg_ip_preserving_secret">Preserving available secret key</string> + <string name="msg_ip_subkey">Processing subkey %s</string> + <string name="msg_ip_subkey_expired">Subkey expired on %s</string> + <string name="msg_ip_subkey_expires">Subkey expires on %s</string> + <string name="msg_ip_subkey_flags">Subkey flags: %s</string> + <string name="msg_ip_subkey_flags_ces">Subkey flags: certify, encrypt, sign</string> + <string name="msg_ip_subkey_flags_cex">Subkey flags: certify, encrypt</string> + <string name="msg_ip_subkey_flags_cxs">Subkey flags: certify, sign</string> + <string name="msg_ip_subkey_flags_xes">Subkey flags: encrypt, sign</string> + <string name="msg_ip_subkey_flags_cxx">Subkey flags: certify</string> + <string name="msg_ip_subkey_flags_xex">Subkey flags: encrypt</string> + <string name="msg_ip_subkey_flags_xxs">Subkey flags: sign</string> + <string name="msg_ip_subkey_flags_xxx">Subkey flags: none</string> + <string name="msg_ip_success">Successfully imported public keyring</string> + <string name="msg_ip_reinsert_secret">Re-inserting secret key</string> + <string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string> + <string name="msg_ip_uid_cert_error">Error processing certificate!</string> + <string name="msg_ip_uid_cert_good">Found good certificate from %1$s (%2$s)</string> + <string name="msg_ip_uid_certs_unknown">Ignoring %s certificates from unknown pubkeys</string> + <string name="msg_ip_uid_classifying">Classifying user ids, using %s trusted signatures</string> + <string name="msg_ip_uid_reorder">Re-ordering user ids</string> + <string name="msg_ip_uid_processing">Processing user id %s</string> + <string name="msg_ip_uid_revoked">Found uid revocation certificate</string> + <string name="msg_ip_uid_self_good">Found good self certificate</string> + <string name="msg_is_bad_type_public">Tried to import public keyring as secret. This is a bug, please file a report!</string> + + <!-- Import Secret log entries --> + <string name="msg_is">Importing secret key %s</string> + <string name="msg_is_importing_subkeys">Processing secret subkeys</string> + <string name="msg_is_io_excption">Error encoding keyring</string> + <string name="msg_is_subkey_nonexistent">Subkey %s unavailable in public key</string> + <string name="msg_is_subkey_ok">Marked %s as available</string> + <string name="msg_is_subkey_stripped">Marked %s as stripped</string> + <string name="msg_is_success">Successfully imported secret keyring</string> + + <!-- Keyring Canonicalization log entries --> + <string name="msg_kc">Canonicalizing keyring %s</string> + <string name="msg_kc_master">Processing master key</string> + <string name="msg_kc_master_success">OK</string> + <string name="msg_kc_revoke_bad_err">Removing bad keyring revocation certificate</string> + <string name="msg_kc_revoke_bad_local">Removing keyring revocation certificate with "local" flag</string> + <string name="msg_kc_revoke_bad_time">Removing keyring revocation certificate with future timestamp</string> + <string name="msg_kc_revoke_bad_type">Removing master key certificate of unknown type (%s)</string> + <string name="msg_kc_revoke_bad">Removing bad keyring revocation certificate</string> + <string name="msg_kc_revoke_dup">Removing redundant keyring revocation certificate</string> + <string name="msg_kc_sub">Processing subkey %s</string> + <string name="msg_kc_sub_bad">Removing invalid subkey binding certificate</string> + <string name="msg_kc_sub_bad_err">Removing bad subkey binding certificate</string> + <string name="msg_kc_sub_bad_local">Removing subkey binding certificate with "local" flag</string> + <string name="msg_kc_sub_bad_keyid">Subkey binding issuer id mismatch</string> + <string name="msg_kc_sub_bad_time">Removing subkey binding certificate with future timestamp</string> + <string name="msg_kc_sub_bad_type">Unknown subkey certificate type: %s</string> + <string name="msg_kc_sub_primary_bad">Removing subkey binding certificate due to invalid primary binding certificate</string> + <string name="msg_kc_sub_primary_bad_err">Removing subkey binding certificate due to bad primary binding certificate</string> + <string name="msg_kc_sub_primary_none">Removing subkey binding certificate due to missing primary binding certificate</string> + <string name="msg_kc_sub_no_cert">No valid certificate found for %s, removing from ring</string> + <string name="msg_kc_sub_revoke_bad_err">Removing bad subkey revocation key</string> + <string name="msg_kc_sub_revoke_bad">Removing bad subkey revocation key</string> + <string name="msg_kc_sub_revoke_dup">Removing redundant keyring revocation key</string> + <string name="msg_kc_sub_success">Subkey binding OK</string> + <string name="msg_kc_success">Keyring canonicalization successful</string> + <string name="msg_kc_success_removed">Keyring canonicalization successful, removed %s certificates</string> + <string name="msg_kc_uid_bad_err">Removing bad self certificate for user id %s</string> + <string name="msg_kc_uid_bad_local">Removing user id certificate with "local" flag</string> + <string name="msg_kc_uid_bad_time">Removing user id with future timestamp</string> + <string name="msg_kc_uid_bad_type">Removing user id certificate of unknown type (%s)</string> + <string name="msg_kc_uid_bad">Removing bad self certificate for user id "%s"</string> + <string name="msg_kc_uid_dup">Removing outdated self certificate for user id "%s"</string> + <string name="msg_kc_uid_revoke_dup">Removing redundant revocation certificate for user id "%s"</string> + <string name="msg_kc_uid_revoke_old">Removing outdated revocation certificate for user id "%s"</string> + <!-- unsorted --> <string name="section_certifier_id">Certifier</string> <string name="section_cert">Certificate Details</string> @@ -523,43 +613,4 @@ <string name="info_no_manual_account_creation">Do not create OpenKeychain-Accounts manually.\nFor more information, see Help.</string> <string name="contact_show_key">Show key (%s)</string> - <!-- Import log entries --> - <string name="msg_ip_apply_batch">Applying insert batch operation.</string> - <string name="msg_ip_bad_type_secret">Tried to import secret keyring as public. This is a bug, please file a report!</string> - <string name="msg_ip_delete_old_fail">No old key deleted (creating a new one?)</string> - <string name="msg_ip_delete_old_ok">Deleted old key from database</string> - <string name="msg_ip_encode_fail">Operation failed due to encoding error</string> - <string name="msg_ip_fail_io_exc">Operation failed due to i/o error</string> - <string name="msg_ip_fail_op_ex">Operation failed due to database error</string> - <string name="msg_ip_fail_remote_ex">Operation failed due to internal error</string> - <string name="msg_ip_importing">Importing public keyring</string> - <string name="msg_ip_insert_keyring">Inserting keyring data</string> - <string name="msg_ip_insert_subkey">Inserting subkey %s</string> - <string name="msg_ip_insert_subkeys">Inserting subkeys</string> - <string name="msg_ip_preserving_secret">Preserving available secret key</string> - <string name="msg_ip_reinsert_secret">Re-inserting secret key</string> - <string name="msg_ip_success">Successfully inserted public keyring</string> - <string name="msg_ip_trust_retrieve">Retrieving trusted keys</string> - <string name="msg_ip_trust_using">Using %s trusted keys</string> - <string name="msg_ip_trust_using_sec">Secret key available, self certificates are trusted</string> - <string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string> - <string name="msg_ip_uid_cert_error">Error processing certificate!</string> - <string name="msg_ip_uid_cert_good">Found good certificate from %s</string> - <string name="msg_ip_uid_certs_unknown">Ignored %s certificates from unknown pubkeys</string> - <string name="msg_ip_uid_classifying">Classifying user ids</string> - <string name="msg_ip_uid_insert">Inserting user ids</string> - <string name="msg_ip_uid_processing">Processing user id %s</string> - <string name="msg_ip_uid_self_bad">Bad self certificate encountered!</string> - <string name="msg_ip_uid_self_good">Found good self certificate</string> - <string name="msg_ip_uid_self_ignoring_old">Ignoring older self certificate</string> - <string name="msg_ip_uid_self_newer">Using more recent good self certificate</string> - <string name="msg_is_bad_type_public">Tried to import public keyring as secret. This is a bug, please file a report!</string> - <string name="msg_is_importing">Importing secret key %s</string> - <string name="msg_is_importing_subkeys">Processing secret subkeys</string> - <string name="msg_is_io_excption">Error encoding keyring</string> - <string name="msg_is_subkey_nonexistent">Subkey %s unavailable in public key</string> - <string name="msg_is_subkey_ok">Marked %s as available</string> - <string name="msg_is_subkey_stripped">Marked %s as stripped</string> - <string name="msg_is_success">Successfully inserted secret keyring</string> - </resources> diff --git a/extern/SuperToasts b/extern/SuperToasts new file mode 160000 +Subproject 8578cfe6917cf16a9f123c1964e4bbff2a15be5 diff --git a/settings.gradle b/settings.gradle index d7ca15f82..282c8a234 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,4 +11,5 @@ include ':extern:spongycastle:pg' include ':extern:spongycastle:pkix' include ':extern:spongycastle:prov' include ':extern:AppMsg:library' +include ':extern:SuperToasts:supertoasts' include ':extern:dnsjava' |