diff options
Diffstat (limited to 'OpenKeychain/src/main/java')
54 files changed, 1624 insertions, 823 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 67fa30a44..30d855a74 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain; import android.os.Environment; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.jce.provider.BouncyCastleProvider; import java.io.File; @@ -26,6 +28,8 @@ import java.io.File; public final class Constants { public static final boolean DEBUG = BuildConfig.DEBUG; + public static final boolean DEBUG_LOG_DB_QUERIES = false; + public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false; public static final String TAG = "Keychain"; @@ -33,6 +37,7 @@ public final class Constants { public static final String ACCOUNT_NAME = "OpenKeychain"; public static final String ACCOUNT_TYPE = PACKAGE_NAME + ".account"; + public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key"; // as defined in http://tools.ietf.org/html/rfc3156, section 7 public static final String NFC_MIME = "application/pgp-keys"; @@ -49,8 +54,6 @@ public final class Constants { public static final String INTENT_PREFIX = PACKAGE_NAME + ".action."; public static final String EXTRA_PREFIX = PACKAGE_NAME + "."; - public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key"; - public static final int TEMPFILE_TTL = 24 * 60 * 60 * 1000; // 1 day public static final String SAFESLINGER_SERVER = "safeslinger-openpgp.appspot.com"; @@ -61,24 +64,17 @@ public final class Constants { } public static final class Pref { - public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm"; - public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm"; - public static final String DEFAULT_ASCII_ARMOR = "defaultAsciiArmor"; - public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression"; - public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression"; public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl"; public static final String PASSPHRASE_CACHE_SUBS = "passphraseCacheSubs"; public static final String LANGUAGE = "language"; public static final String KEY_SERVERS = "keyServers"; public static final String PREF_DEFAULT_VERSION = "keyServersDefaultVersion"; - public static final String WRITE_VERSION_HEADER = "writeVersionHeader"; public static final String FIRST_TIME = "firstTime"; - public static final String SHOW_ADVANCED_TABS = "showAdvancedTabs"; public static final String CACHED_CONSOLIDATE = "cachedConsolidate"; public static final String SEARCH_KEYSERVER = "search_keyserver_pref"; public static final String SEARCH_KEYBASE = "search_keybase_pref"; public static final String USE_DEFAULT_YUBIKEY_PIN = "useDefaultYubikeyPin"; - public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN="useNumKeypadForYubikeyPin"; + public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN = "useNumKeypadForYubikeyPin"; } public static final class Defaults { @@ -90,4 +86,5 @@ public final class Constants { public static final int none = 0; public static final int symmetric = -1; } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java index 8f10377cd..20dba95e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -505,10 +505,6 @@ public class ImportExportOperation extends BaseOperation { // Create an output stream try { arOutStream = new ArmoredOutputStream(outStream); - String version = PgpHelper.getVersionForHeader(mContext); - if (version != null) { - arOutStream.setHeader("Version", version); - } log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId)); @@ -533,10 +529,6 @@ public class ImportExportOperation extends BaseOperation { if (exportSecret && cursor.getInt(3) > 0) { try { arOutStream = new ArmoredOutputStream(outStream); - String version = PgpHelper.getVersionForHeader(mContext); - if (version != null) { - arOutStream.setHeader("Version", version); - } // export secret key part log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index f79900aab..a96cec8cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -38,6 +38,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** Represent the result of an operation. * @@ -51,6 +53,56 @@ import java.util.List; public abstract class OperationResult implements Parcelable { public static final String EXTRA_RESULT = "operation_result"; + public static final UUID NULL_UUID = new UUID(0,0); + + /** + * A HashMap of UUID:OperationLog which contains logs that we don't need + * to care about. This is used such that when we become parceled, we are + * well below the 1Mbit boundary that is specified. + */ + private static ConcurrentHashMap<UUID, OperationLog> dehydratedLogs; + static { + // Static initializer for ConcurrentHashMap + dehydratedLogs = new ConcurrentHashMap<UUID,OperationLog>(); + } + + /** + * Dehydrate a log (such that it is available after deparcelization) + * + * Returns the NULL uuid (0) if you hand it null. + * @param log An OperationLog to dehydrate + * @return a UUID, the ticket for your dehydrated log + * + */ + private static UUID dehydrateLog(OperationLog log) { + if(log == null) { + return NULL_UUID; + } + else { + UUID ticket = UUID.randomUUID(); + dehydratedLogs.put(ticket, log); + return ticket; + } + } + + /*** + * Rehydrate a log after going through parcelization, invalidating its place in the + * dehydration pool. + * This is used such that when parcelized, the parcel is no larger than 1mbit. + * @param ticket A UUID ticket that identifies the log in question. + * @return An OperationLog. + */ + private static OperationLog rehydrateLog(UUID ticket) { + // UUID.equals isn't well documented; we use compareTo instead. + if( NULL_UUID.compareTo(ticket) == 0 ) { + return null; + } + else { + OperationLog log = dehydratedLogs.get(ticket); + dehydratedLogs.remove(ticket); + return log; + } + } /** Holds the overall result, the number specifying varying degrees of success: * - The first bit is 0 on overall success, 1 on overall failure @@ -65,7 +117,7 @@ public abstract class OperationResult implements Parcelable { public static final int RESULT_WARNINGS = 4; /// A list of log entries tied to the operation result. - final OperationLog mLog; + protected OperationLog mLog; public OperationResult(int result, OperationLog log) { mResult = result; @@ -74,8 +126,11 @@ public abstract class OperationResult implements Parcelable { public OperationResult(Parcel source) { mResult = source.readInt(); - mLog = new OperationLog(); - mLog.addAll(source.createTypedArrayList(LogEntryParcel.CREATOR)); + long mostSig = source.readLong(); + long leastSig = source.readLong(); + UUID mTicket = new UUID(mostSig, leastSig); + // fetch the dehydrated log out of storage (this removes it from the dehydration pool) + mLog = rehydrateLog(mTicket); } public int getResult() { @@ -571,6 +626,7 @@ public abstract class OperationResult implements Parcelable { MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data), MSG_DC_ERROR_NO_KEY (LogLevel.ERROR, R.string.msg_dc_error_no_key), MSG_DC_ERROR_PGP_EXCEPTION (LogLevel.ERROR, R.string.msg_dc_error_pgp_exception), + MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO (LogLevel.ERROR, R.string.msg_dc_error_unsupported_hash_algo), MSG_DC_INTEGRITY_CHECK_OK (LogLevel.INFO, R.string.msg_dc_integrity_check_ok), MSG_DC_OK_META_ONLY (LogLevel.OK, R.string.msg_dc_ok_meta_only), MSG_DC_OK (LogLevel.OK, R.string.msg_dc_ok), @@ -585,6 +641,7 @@ public abstract class OperationResult implements Parcelable { MSG_DC_TRAIL_SYM (LogLevel.DEBUG, R.string.msg_dc_trail_sym), MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown), MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking), + MSG_DC_OLD_SYMMETRIC_ENCRYPTION_ALGO (LogLevel.WARN, R.string.msg_dc_old_symmetric_encryption_algo), // verify signed literal data MSG_VL (LogLevel.INFO, R.string.msg_vl), @@ -707,6 +764,13 @@ public abstract class OperationResult implements Parcelable { MSG_DEL_CONSOLIDATE (LogLevel.DEBUG, R.string.msg_del_consolidate), MSG_DEL_OK (LogLevel.OK, R.plurals.msg_del_ok), MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail), + + //export log + MSG_EXPORT_LOG(LogLevel.START,R.string.msg_export_log_start), + MSG_EXPORT_LOG_EXPORT_ERROR_NO_FILE(LogLevel.ERROR,R.string.msg_export_log_error_no_file), + MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN(LogLevel.ERROR,R.string.msg_export_log_error_fopen), + MSG_EXPORT_LOG_EXPORT_ERROR_WRITING(LogLevel.ERROR,R.string.msg_export_log_error_writing), + MSG_EXPORT_LOG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_log_success), ; public final int mMsgId; @@ -739,9 +803,11 @@ public abstract class OperationResult implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mResult); - if (mLog != null) { - dest.writeTypedList(mLog.toList()); - } + // Get a ticket for our log. + UUID mTicket = dehydrateLog(mLog); + // And write out the UUID most and least significant bits. + dest.writeLong(mTicket.getMostSignificantBits()); + dest.writeLong(mTicket.getLeastSignificantBits()); } public static class OperationLog implements Iterable<LogEntryParcel> { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 40f2f48ad..fe5db8c6d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; @@ -44,6 +45,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; @@ -137,7 +139,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { // It means the passphrase is empty return SecretKeyType.PASSPHRASE_EMPTY; } catch (PGPException e) { - HashMap<String,String> notation = getRing().getLocalNotationData(); + HashMap<String, String> notation = getRing().getLocalNotationData(); if (notation.containsKey("unlock.pin@sufficientlysecure.org") && "1".equals(notation.get("unlock.pin@sufficientlysecure.org"))) { return SecretKeyType.PIN; @@ -176,33 +178,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } /** - * Returns a list of all supported hash algorithms. This list is currently hardcoded to return - * a limited set of algorithms supported by Yubikeys. - * - * @return + * Returns a list of all supported hash algorithms. */ - public LinkedList<Integer> getSupportedHashAlgorithms() { - LinkedList<Integer> supported = new LinkedList<>(); - - if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { - // No support for MD5 - supported.add(HashAlgorithmTags.RIPEMD160); - supported.add(HashAlgorithmTags.SHA1); - supported.add(HashAlgorithmTags.SHA224); - supported.add(HashAlgorithmTags.SHA256); - supported.add(HashAlgorithmTags.SHA384); - supported.add(HashAlgorithmTags.SHA512); // preferred is latest - } else { - supported.add(HashAlgorithmTags.MD5); - supported.add(HashAlgorithmTags.RIPEMD160); - supported.add(HashAlgorithmTags.SHA1); - supported.add(HashAlgorithmTags.SHA224); - supported.add(HashAlgorithmTags.SHA256); - supported.add(HashAlgorithmTags.SHA384); - supported.add(HashAlgorithmTags.SHA512); // preferred is latest - } + public ArrayList<Integer> getSupportedHashAlgorithms() { + // TODO: intersection between preferred hash algos of this key and PgpConstants.PREFERRED_HASH_ALGORITHMS + // choose best algo - return supported; + return PgpConstants.sPreferredHashAlgorithms; } private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash, @@ -358,7 +340,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } // HACK, for TESTING ONLY!! - PGPPrivateKey getPrivateKey () { + PGPPrivateKey getPrivateKey() { return mPrivateKey; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index b5f6a5b09..97b5fa6fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -19,11 +19,11 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -45,7 +45,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { public CanonicalizedSecretKeyRing(byte[] blob, boolean isRevoked, int verified) { super(verified); - PGPObjectFactory factory = new PGPObjectFactory(blob); + JcaPGPObjectFactory factory = new JcaPGPObjectFactory(blob); PGPKeyRing keyRing = null; try { if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java new file mode 100644 index 000000000..90991ba15 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.pgp; + +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; + +import java.util.ArrayList; + +public class PgpConstants { + + public static ArrayList<Integer> sPreferredSymmetricAlgorithms = new ArrayList<>(); + public static ArrayList<Integer> sPreferredHashAlgorithms = new ArrayList<>(); + public static ArrayList<Integer> sPreferredCompressionAlgorithms = new ArrayList<>(); + + // TODO: use hashmaps for contains in O(1) and intersections! + + /* + * Most preferred is first + * These arrays are written as preferred algorithms into the keys on creation. + * Other implementations may choose to honor this selection. + * + * These lists also define the only algorithms which are used in OpenKeychain. + * We do not support algorithms such as MD5 + */ + static { + sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_256); + sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_192); + sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_128); + sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.TWOFISH); + + // NOTE: some implementations do not support SHA512, thus we choose SHA256 as default (Mailvelope?) + sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA256); + sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA512); + sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA384); + sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA224); + sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA1); + sPreferredHashAlgorithms.add(HashAlgorithmTags.RIPEMD160); + + sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.ZLIB); + sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.BZIP2); + sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.ZIP); + } + + /* + * Note: s2kcount is a number between 0 and 0xff that controls the + * number of times to iterate the password hash before use. More + * iterations are useful against offline attacks, as it takes more + * time to check each password. The actual number of iterations is + * rather complex, and also depends on the hash function in use. + * Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give + * you more iterations. As a rough rule of thumb, when using + * SHA256 as the hashing function, 0x10 gives you about 64 + * iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0, + * or about 1 million iterations. The maximum you can go to is + * 0xff, or about 2 million iterations. + * from http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html + * + * Bouncy Castle default: 0x60 + * kbsriram proposes: 0xc0 + * OpenKeychain: 0x90 + */ + public static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x90; + public static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA256; + public static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256; + public static final int SECRET_KEY_SIGNATURE_HASH_ALGO = HashAlgorithmTags.SHA256; + // NOTE: only SHA1 is supported for key checksum calculations in OpenPGP, + // see http://tools.ietf.org/html/rfc488 0#section-5.5.3 + public static final int SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO = HashAlgorithmTags.SHA1; + + public static interface OpenKeychainSymmetricKeyAlgorithmTags extends SymmetricKeyAlgorithmTags { + public static final int USE_PREFERRED = -1; + } + + public static interface OpenKeychainHashAlgorithmTags extends HashAlgorithmTags { + public static final int USE_PREFERRED = -1; + } + + public static interface OpenKeychainCompressionAlgorithmTags extends CompressionAlgorithmTags { + public static final int USE_PREFERRED = -1; + } + + public static int[] getAsArray(ArrayList<Integer> list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 2ba0b6231..14bc56538 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -563,6 +563,7 @@ public class PgpDecryptVerify extends BaseOperation { log.add(LogType.MSG_DC_PREP_STREAMS, indent); // we made sure above one of these two would be true + int symmetricEncryptionAlgo; if (symmetricPacketFound) { currentProgress += 2; updateProgress(R.string.progress_preparing_streams, currentProgress, 100); @@ -576,6 +577,7 @@ public class PgpDecryptVerify extends BaseOperation { clear = encryptedDataSymmetric.getDataStream(decryptorFactory); encryptedData = encryptedDataSymmetric; + symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory); } else if (asymmetricPacketFound) { currentProgress += 2; updateProgress(R.string.progress_extracting_key, currentProgress, 100); @@ -598,6 +600,8 @@ public class PgpDecryptVerify extends BaseOperation { PublicKeyDataDecryptorFactory decryptorFactory = secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey); clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); + + symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) { log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); DecryptVerifyResult result = @@ -614,6 +618,11 @@ public class PgpDecryptVerify extends BaseOperation { return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } + // Warn about old encryption algorithms! + if (!PgpConstants.sPreferredSymmetricAlgorithms.contains(symmetricEncryptionAlgo)) { + log.add(LogType.MSG_DC_OLD_SYMMETRIC_ENCRYPTION_ALGO, indent + 1); + } + JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear); Object dataChunk = plainFact.nextObject(); OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); @@ -811,6 +820,13 @@ public class PgpDecryptVerify extends BaseOperation { } else { log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); } + + // Don't allow verification of old hash algorithms! + if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) { + validSignature = false; + log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1); + } + signatureResultBuilder.setValidSignature(validSignature); } @@ -936,6 +952,13 @@ public class PgpDecryptVerify extends BaseOperation { } else { log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); } + + // Don't allow verification of old hash algorithms! + if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) { + validSignature = false; + log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1); + } + signatureResultBuilder.setValidSignature(validSignature); } catch (SignatureException e) { @@ -1024,6 +1047,13 @@ public class PgpDecryptVerify extends BaseOperation { } else { log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1); } + + // Don't allow verification of old hash algorithms! + if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) { + validSignature = false; + log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1); + } + signatureResultBuilder.setValidSignature(validSignature); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index 12de2f637..d8b86a18c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -47,26 +47,6 @@ public class PgpHelper { ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", Pattern.DOTALL); - public static String getVersion(Context context) { - String version; - try { - PackageInfo pi = context.getPackageManager().getPackageInfo(Constants.PACKAGE_NAME, 0); - version = pi.versionName; - return version; - } catch (NameNotFoundException e) { - Log.e(Constants.TAG, "Version could not be retrieved!", e); - return "0.0"; - } - } - - public static String getVersionForHeader(Context context) { - if(Preferences.getPreferences(context).getWriteVersionHeader()){ - return "OpenKeychain v" + getVersion(context); - } else { - return null; - } - } - /** * Deletes file securely by overwriting it with random data before deleting it. * <p/> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 1a251eb79..8fb5392e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -18,9 +18,7 @@ package org.sufficientlysecure.keychain.pgp; -import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.bcpg.sig.Features; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.spec.ElGamalParameterSpec; @@ -90,48 +88,6 @@ public class PgpKeyOperation { private Stack<Progressable> mProgress; private AtomicBoolean mCancelled; - // most preferred is first - private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{ - SymmetricKeyAlgorithmTags.AES_256, - SymmetricKeyAlgorithmTags.AES_192, - SymmetricKeyAlgorithmTags.AES_128, - SymmetricKeyAlgorithmTags.CAST5 - }; - private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{ - HashAlgorithmTags.SHA512, - HashAlgorithmTags.SHA384, - HashAlgorithmTags.SHA224, - HashAlgorithmTags.SHA256, - HashAlgorithmTags.RIPEMD160 - }; - private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{ - CompressionAlgorithmTags.ZLIB, - CompressionAlgorithmTags.BZIP2, - CompressionAlgorithmTags.ZIP - }; - - /* - * Note: s2kcount is a number between 0 and 0xff that controls the - * number of times to iterate the password hash before use. More - * iterations are useful against offline attacks, as it takes more - * time to check each password. The actual number of iterations is - * rather complex, and also depends on the hash function in use. - * Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give - * you more iterations. As a rough rule of thumb, when using - * SHA256 as the hashing function, 0x10 gives you about 64 - * iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0, - * or about 1 million iterations. The maximum you can go to is - * 0xff, or about 2 million iterations. - * from http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html - * - * Bouncy Castle default: 0x60 - * kbsriram proposes 0xc0 - * we use 0x90, a good trade-off between usability and security against offline attacks - */ - private static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x90; - private static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA256; - private static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256; - public PgpKeyOperation(Progressable progress) { super(); if (progress != null) { @@ -345,14 +301,14 @@ public class PgpKeyOperation { // Build key encrypter and decrypter based on passphrase PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder() - .build().get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); + .build().get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( - SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) + PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, + encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); - // NOTE: only SHA1 is supported for key checksum calculations. PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() - .build().get(HashAlgorithmTags.SHA1); + .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO); PGPSecretKey masterSecretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), sha1Calc, true, keyEncryptor); @@ -879,14 +835,14 @@ public class PgpKeyOperation { PGPSecretKey sKey; { // Build key encrypter and decrypter based on passphrase PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder() - .build().get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); + .build().get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( - SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) + PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, + PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - // NOTE: only SHA1 is supported for key checksum calculations. PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() - .build().get(HashAlgorithmTags.SHA1); + .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO); sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, sha1Calc, false, keyEncryptor); } @@ -1025,7 +981,8 @@ public class PgpKeyOperation { // add packet with EMPTY notation data (updates old one, but will be stripped later) PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); { // set subpackets @@ -1051,7 +1008,8 @@ public class PgpKeyOperation { // add packet with "pin" notation data PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); { // set subpackets @@ -1098,12 +1056,13 @@ public class PgpKeyOperation { OperationLog log, int indent) throws PGPException { PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build() - .get(SECRET_KEY_ENCRYPTOR_HASH_ALGO); + .get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO); PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); // Build key encryptor based on new passphrase PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( - SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT) + PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, + PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( newPassphrase.toCharArray()); @@ -1236,7 +1195,8 @@ public class PgpKeyOperation { int flags, long expiry) throws IOException, PGPException, SignatureException { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); @@ -1253,9 +1213,12 @@ public class PgpKeyOperation { * error than be ignored. */ /* non-critical subpackets: */ - hashedPacketsGen.setPreferredSymmetricAlgorithms(false, PREFERRED_SYMMETRIC_ALGORITHMS); - hashedPacketsGen.setPreferredHashAlgorithms(false, PREFERRED_HASH_ALGORITHMS); - hashedPacketsGen.setPreferredCompressionAlgorithms(false, PREFERRED_COMPRESSION_ALGORITHMS); + hashedPacketsGen.setPreferredSymmetricAlgorithms(false, + PgpConstants.getAsArray(PgpConstants.sPreferredSymmetricAlgorithms)); + hashedPacketsGen.setPreferredHashAlgorithms(false, + PgpConstants.getAsArray(PgpConstants.sPreferredHashAlgorithms)); + hashedPacketsGen.setPreferredCompressionAlgorithms(false, + PgpConstants.getAsArray(PgpConstants.sPreferredCompressionAlgorithms)); hashedPacketsGen.setPrimaryUserID(false, primary); /* critical subpackets: we consider those important for a modern pgp implementation */ @@ -1279,7 +1242,8 @@ public class PgpKeyOperation { PGPUserAttributeSubpacketVector vector) throws IOException, PGPException, SignatureException { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); @@ -1298,7 +1262,8 @@ public class PgpKeyOperation { PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) throws IOException, PGPException, SignatureException { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); @@ -1312,7 +1277,7 @@ public class PgpKeyOperation { PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey) throws IOException, PGPException, SignatureException { PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512) + masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); @@ -1356,7 +1321,7 @@ public class PgpKeyOperation { PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); subHashedPacketsGen.setSignatureCreationTime(false, creationTime); PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - pKey.getAlgorithm(), HashAlgorithmTags.SHA512) + pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); @@ -1377,7 +1342,7 @@ public class PgpKeyOperation { } PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA512) + masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java index 9318be006..1ed0a4720 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java @@ -12,10 +12,10 @@ public class PgpSignEncryptInput { protected int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED; protected long[] mEncryptionMasterKeyIds = null; protected String mSymmetricPassphrase = null; - protected int mSymmetricEncryptionAlgorithm = 0; + protected int mSymmetricEncryptionAlgorithm = PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED; protected long mSignatureMasterKeyId = Constants.key.none; protected Long mSignatureSubKeyId = null; - protected int mSignatureHashAlgorithm = 0; + protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED; protected String mSignaturePassphrase = null; protected long mAdditionalEncryptId = Constants.key.none; protected byte[] mNfcSignedHash = null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 2fa01d241..81cc2c847 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -25,7 +25,6 @@ import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.BCPGOutputStream; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.openpgp.PGPCompressedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedDataGenerator; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPLiteralData; @@ -58,6 +57,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.SignatureException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; @@ -206,12 +206,12 @@ public class PgpSignEncryptOperation extends BaseOperation { return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } - // check if hash algo is supported + // Use preferred hash algo int requestedAlgorithm = input.getSignatureHashAlgorithm(); - LinkedList<Integer> supported = signingKey.getSupportedHashAlgorithms(); - if (requestedAlgorithm == 0) { + ArrayList<Integer> supported = signingKey.getSupportedHashAlgorithms(); + if (requestedAlgorithm == PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) { // get most preferred - input.setSignatureHashAlgorithm(supported.getLast()); + input.setSignatureHashAlgorithm(supported.get(0)); } else if (!supported.contains(requestedAlgorithm)) { log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); @@ -222,9 +222,13 @@ public class PgpSignEncryptOperation extends BaseOperation { /* Initialize PGPEncryptedDataGenerator for later usage */ PGPEncryptedDataGenerator cPk = null; if (enableEncryption) { + + // Use preferred encryption algo int algo = input.getSymmetricEncryptionAlgorithm(); - if (algo == 0) { - algo = PGPEncryptedData.AES_128; + if (algo == PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED) { + // get most preferred + // TODO: get from recipients + algo = PgpConstants.sPreferredSymmetricAlgorithms.get(0); } // has Integrity packet enabled! JcePGPDataEncryptorBuilder encryptorBuilder = diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 2601c1f69..4ccfc3cd9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -650,7 +650,7 @@ public class KeychainProvider extends ContentProvider { cursor.setNotificationUri(getContext().getContentResolver(), uri); } - if (Constants.DEBUG) { + if (Constants.DEBUG && Constants.DEBUG_LOG_DB_QUERIES) { Log.d(Constants.TAG, "Query: " + qb.buildQuery(projection, selection, selectionArgs, null, null, 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 d947ae053..6366e8536 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -1372,8 +1372,7 @@ public class ProviderHelper { UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(data); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - String version = PgpHelper.getVersionForHeader(mContext); - keyRing.encodeArmored(bos, version); + keyRing.encodeArmored(bos, null); String armoredKey = bos.toString("UTF-8"); Log.d(Constants.TAG, "armoredKey:" + armoredKey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 1a65b1bee..03fa41984 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -30,13 +30,16 @@ import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpApi; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; +import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; @@ -52,6 +55,7 @@ import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.io.InputStream; @@ -258,8 +262,8 @@ public class OpenPgpService extends RemoteService { .setEnableAsciiArmorOutput(asciiArmor) .setCleartextSignature(cleartextSign) .setDetachedSignature(!cleartextSign) - .setVersionHeader(PgpHelper.getVersionForHeader(this)) - .setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) + .setVersionHeader(null) + .setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) .setSignatureMasterKeyId(accSettings.getKeyId()) .setNfcState(nfcSignedHash, nfcCreationDate); @@ -356,9 +360,9 @@ public class OpenPgpService extends RemoteService { PgpSignEncryptInput pseInput = new PgpSignEncryptInput(); pseInput.setEnableAsciiArmorOutput(asciiArmor) - .setVersionHeader(PgpHelper.getVersionForHeader(this)) - .setCompressionId(accSettings.getCompression()) - .setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm()) + .setVersionHeader(null) + .setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED) + .setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED) .setEncryptionMasterKeyIds(keyIds) .setFailOnMissingEncryptionKeyIds(true) .setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption @@ -374,7 +378,7 @@ public class OpenPgpService extends RemoteService { } // sign and encrypt - pseInput.setSignatureHashAlgorithm(accSettings.getHashAlgorithm()) + pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) .setSignatureMasterKeyId(accSettings.getKeyId()) .setNfcState(nfcSignedHash, nfcCreationDate); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 62c38d136..60cc404b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -28,9 +28,11 @@ public class CreateKeyActivity extends BaseActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; - public static final int FRAG_ACTION_START = 0; - public static final int FRAG_ACTION_TO_RIGHT = 1; - public static final int FRAG_ACTION_TO_LEFT = 2; + public static enum FragAction { + START, + TO_RIGHT, + TO_LEFT + } @Override public void onCreate(Bundle savedInstanceState) { @@ -42,7 +44,7 @@ public class CreateKeyActivity extends BaseActivity { getIntent().getStringExtra(EXTRA_NAME), getIntent().getStringExtra(EXTRA_EMAIL) ); - loadFragment(null, frag, FRAG_ACTION_START); + loadFragment(null, frag, FragAction.START); } @Override @@ -50,7 +52,7 @@ public class CreateKeyActivity extends BaseActivity { setContentView(R.layout.create_key_activity); } - public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) { + public void loadFragment(Bundle savedInstanceState, Fragment fragment, FragAction action) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -63,15 +65,15 @@ public class CreateKeyActivity extends BaseActivity { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); switch (action) { - case FRAG_ACTION_START: + case START: transaction.setCustomAnimations(0, 0); transaction.replace(R.id.create_key_fragment_container, fragment) .commitAllowingStateLoss(); break; - case FRAG_ACTION_TO_LEFT: + case TO_LEFT: getSupportFragmentManager().popBackStackImmediate(); break; - case FRAG_ACTION_TO_RIGHT: + case TO_RIGHT: transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left, R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right); transaction.addToBackStack(null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index 6e0115342..920488e3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; @@ -117,7 +118,7 @@ public class CreateKeyFinalFragment extends Fragment { mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mCreateKeyActivity.loadFragment(null, null, CreateKeyActivity.FRAG_ACTION_TO_LEFT); + mCreateKeyActivity.loadFragment(null, null, FragAction.TO_LEFT); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java index 8aa9fa6db..b496d40fd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java @@ -20,9 +20,6 @@ package org.sufficientlysecure.keychain.ui; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Patterns; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -32,17 +29,20 @@ import android.widget.AutoCompleteTextView; import android.widget.EditText; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.widget.EmailEditText; +import org.sufficientlysecure.keychain.ui.widget.PasswordEditText; +import org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator.PasswordStrengthView; import org.sufficientlysecure.keychain.util.ContactHelper; -import java.util.regex.Matcher; - public class CreateKeyInputFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; + PasswordStrengthView mPassphraseStrengthView; AutoCompleteTextView mNameEdit; - AutoCompleteTextView mEmailEdit; - EditText mPassphraseEdit; + EmailEditText mEmailEdit; + PasswordEditText mPassphraseEdit; EditText mPassphraseEditAgain; View mCreateButton; @@ -68,9 +68,11 @@ public class CreateKeyInputFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_input_fragment, container, false); + mPassphraseStrengthView = (PasswordStrengthView) view.findViewById(R.id + .create_key_passphrase_strength); mNameEdit = (AutoCompleteTextView) view.findViewById(R.id.create_key_name); - mEmailEdit = (AutoCompleteTextView) view.findViewById(R.id.create_key_email); - mPassphraseEdit = (EditText) view.findViewById(R.id.create_key_passphrase); + mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email); + mPassphraseEdit = (PasswordEditText) view.findViewById(R.id.create_key_passphrase); mPassphraseEditAgain = (EditText) view.findViewById(R.id.create_key_passphrase_again); mCreateButton = view.findViewById(R.id.create_key_button); @@ -94,33 +96,7 @@ public class CreateKeyInputFragment extends Fragment { ContactHelper.getPossibleUserEmails(getActivity()) ) ); - mEmailEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - } - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - } - - @Override - public void afterTextChanged(Editable editable) { - String email = editable.toString(); - if (email.length() > 0) { - Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); - if (emailMatcher.matches()) { - mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_ok, 0); - } else { - mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_bad, 0); - } - } else { - // remove drawable if email is empty - mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - } - }); mNameEdit.setThreshold(1); // Start working from first character mNameEdit.setAdapter( @@ -130,6 +106,15 @@ public class CreateKeyInputFragment extends Fragment { ) ); + // Edit text padding doesn't work via xml (http://code.google.com/p/android/issues/detail?id=77982) + // so we set the right padding programmatically. + mPassphraseEdit.setPadding(mPassphraseEdit.getPaddingLeft(), + mPassphraseEdit.getPaddingTop(), + (int) (56 * getResources().getDisplayMetrics().density), + mPassphraseEdit.getPaddingBottom()); + + mPassphraseEdit.setPasswordStrengthView(mPassphraseStrengthView); + mCreateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -161,7 +146,7 @@ public class CreateKeyInputFragment extends Fragment { ); hideKeyboard(); - mCreateKeyActivity.loadFragment(null, frag, CreateKeyActivity.FRAG_ACTION_TO_RIGHT); + mCreateKeyActivity.loadFragment(null, frag, FragAction.TO_RIGHT); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 8723c7255..60103f344 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; public abstract class DecryptFragment extends Fragment { private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006; @@ -141,16 +142,16 @@ public abstract class DecryptFragment extends Fragment { if (signatureResult.isSignatureOnly()) { mEncryptionText.setText(R.string.decrypt_result_not_encrypted); - KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, KeyFormattingUtils.STATE_NOT_ENCRYPTED); + KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.NOT_ENCRYPTED); } else { mEncryptionText.setText(R.string.decrypt_result_encrypted); - KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, KeyFormattingUtils.STATE_ENCRYPTED); + KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.ENCRYPTED); } switch (signatureResult.getStatus()) { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { mSignatureText.setText(R.string.decrypt_result_signature_certified); - KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_VERIFIED); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(mSignatureKeyId); @@ -161,7 +162,7 @@ public abstract class DecryptFragment extends Fragment { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { mSignatureText.setText(R.string.decrypt_result_signature_uncertified); - KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_UNVERIFIED); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.UNVERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(mSignatureKeyId); @@ -172,11 +173,11 @@ public abstract class DecryptFragment extends Fragment { case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: { mSignatureText.setText(R.string.decrypt_result_signature_missing_key); - KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_UNKNOWN_KEY); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.UNKNOWN_KEY); setSignatureLayoutVisibility(View.VISIBLE); mSignatureAction.setText(R.string.decrypt_result_action_Lookup); - mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_action_download, 0); + mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_file_download_grey_24dp, 0); mSignatureLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -190,7 +191,7 @@ public abstract class DecryptFragment extends Fragment { case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: { mSignatureText.setText(R.string.decrypt_result_signature_expired_key); - KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_EXPIRED); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.EXPIRED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(mSignatureKeyId); @@ -201,7 +202,7 @@ public abstract class DecryptFragment extends Fragment { case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: { mSignatureText.setText(R.string.decrypt_result_signature_revoked_key); - KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_REVOKED); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.REVOKED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(mSignatureKeyId); @@ -212,7 +213,7 @@ public abstract class DecryptFragment extends Fragment { case OpenPgpSignatureResult.SIGNATURE_ERROR: { mSignatureText.setText(R.string.decrypt_result_invalid_signature); - KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_INVALID); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.INVALID); setSignatureLayoutVisibility(View.GONE); @@ -224,9 +225,9 @@ public abstract class DecryptFragment extends Fragment { setSignatureLayoutVisibility(View.GONE); mSignatureText.setText(R.string.decrypt_result_no_signature); - KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, KeyFormattingUtils.STATE_NOT_SIGNED); + KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.NOT_SIGNED); mEncryptionText.setText(R.string.decrypt_result_encrypted); - KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, KeyFormattingUtils.STATE_ENCRYPTED); + KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.ENCRYPTED); valid = true; } @@ -242,7 +243,7 @@ public abstract class DecryptFragment extends Fragment { private void setShowAction(final long signatureKeyId) { mSignatureAction.setText(R.string.decrypt_result_action_show); - mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_action_accounts, 0); + mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_vpn_key_grey_24dp, 0); mSignatureLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java index 54fe369a7..baf445293 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java @@ -28,6 +28,7 @@ public interface EncryptActivityInterface { } public boolean isUseArmor(); + public boolean isUseCompression(); public long getSignatureKey(); public long[] getEncryptionKeys(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index 1286617d3..11b596c24 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -25,11 +25,13 @@ import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuItem; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -66,6 +68,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi private long mSigningKeyId = Constants.key.none; private String mPassphrase = ""; private boolean mUseArmor; + private boolean mUseCompression; private boolean mDeleteAfterEncrypt = false; private boolean mShareAfterEncrypt = false; private ArrayList<Uri> mInputUris; @@ -82,6 +85,11 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi } @Override + public boolean isUseCompression() { + return mUseCompression; + } + + @Override public long getSignatureKey() { return mSigningKeyId; } @@ -196,10 +204,13 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi data.addInputUris(mInputUris); data.addOutputUris(mOutputUris); - data.setCompressionId(Preferences.getPreferences(this).getDefaultMessageCompression()); - - // Always use armor for messages - data.setEnableAsciiArmorOutput(mUseArmor); + if (mUseCompression) { + data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0)); + } else { + data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED); + } + data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); + data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); if (isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); @@ -315,8 +326,6 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi // Handle intent actions handleActions(getIntent()); updateModeFragment(); - - mUseArmor = Preferences.getPreferences(this).getDefaultAsciiArmor(); } @Override @@ -327,7 +336,6 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.encrypt_file_activity, menu); - menu.findItem(R.id.check_use_armor).setChecked(mUseArmor); return super.onCreateOptionsMenu(menu); } @@ -348,21 +356,30 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi item.setChecked(!item.isChecked()); } switch (item.getItemId()) { - case R.id.check_use_symmetric: + case R.id.check_use_symmetric: { mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC; updateModeFragment(); notifyUpdate(); break; - case R.id.check_use_armor: + } + case R.id.check_use_armor: { mUseArmor = item.isChecked(); notifyUpdate(); break; - case R.id.check_delete_after_encrypt: + } + case R.id.check_delete_after_encrypt: { mDeleteAfterEncrypt = item.isChecked(); notifyUpdate(); break; - default: + } + case R.id.check_enable_compression: { + mUseCompression = item.isChecked(); + notifyUpdate(); + break; + } + default: { return super.onOptionsItemSelected(item); + } } return true; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index 2dd861d07..08ff5b962 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -25,12 +25,14 @@ import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuItem; +import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -70,6 +72,7 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv private ArrayList<Uri> mInputUris; private ArrayList<Uri> mOutputUris; private String mMessage = ""; + private boolean mUseCompression; public boolean isModeSymmetric() { return MODE_SYMMETRIC == mCurrentMode; @@ -81,6 +84,11 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv } @Override + public boolean isUseCompression() { + return mUseCompression; + } + + @Override public long getSignatureKey() { return mSigningKeyId; } @@ -189,7 +197,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv data.setBytes(mMessage.getBytes()); data.setCleartextSignature(true); - data.setCompressionId(Preferences.getPreferences(this).getDefaultMessageCompression()); + if (mUseCompression) { + data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0)); + } else { + data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED); + } + data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); + data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED); // Always use armor for messages data.setEnableAsciiArmorOutput(true); @@ -328,13 +342,20 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv item.setChecked(!item.isChecked()); } switch (item.getItemId()) { - case R.id.check_use_symmetric: + case R.id.check_use_symmetric: { mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC; updateModeFragment(); notifyUpdate(); break; - default: + } + case R.id.check_enable_compression: { + mUseCompression = item.isChecked(); + notifyUpdate(); + break; + } + default: { return super.onOptionsItemSelected(item); + } } return true; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index 2eb35351e..cd6cdf4d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -32,10 +32,9 @@ public class HelpActivity extends BaseActivity { public static final int TAB_START = 0; public static final int TAB_FAQ = 1; - public static final int TAB_WOT = 2; - public static final int TAB_NFC = 3; - public static final int TAB_CHANGELOG = 4; - public static final int TAB_ABOUT = 5; + public static final int TAB_TRUST = 2; + public static final int TAB_CHANGELOG = 3; + public static final int TAB_ABOUT = 4; ViewPager mViewPager; private PagerTabStripAdapter mTabsAdapter; @@ -69,21 +68,11 @@ public class HelpActivity extends BaseActivity { mTabsAdapter.addTab(HelpHtmlFragment.class, startBundle, getString(R.string.help_tab_start)); - Bundle faqBundle = new Bundle(); - faqBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_faq); - mTabsAdapter.addTab(HelpHtmlFragment.class, faqBundle, - getString(R.string.help_tab_faq)); - Bundle wotBundle = new Bundle(); - wotBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_wot); + wotBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_certification); mTabsAdapter.addTab(HelpHtmlFragment.class, wotBundle, getString(R.string.help_tab_wot)); - Bundle nfcBundle = new Bundle(); - nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam); - mTabsAdapter.addTab(HelpHtmlFragment.class, nfcBundle, - getString(R.string.help_tab_nfc_beam)); - Bundle changelogBundle = new Bundle(); changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog); mTabsAdapter.addTab(HelpHtmlFragment.class, changelogBundle, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index b56da463a..43d893fa6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -70,6 +70,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.FabContainer; @@ -77,7 +78,6 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; -import java.util.Date; import java.util.HashMap; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; @@ -268,7 +268,7 @@ public class KeyListFragment extends LoaderFragment KeyRings.MASTER_KEY_ID, KeyRings.USER_ID, KeyRings.IS_REVOKED, - KeyRings.EXPIRY, + KeyRings.IS_EXPIRED, KeyRings.VERIFIED, KeyRings.HAS_ANY_SECRET }; @@ -276,7 +276,7 @@ public class KeyListFragment extends LoaderFragment static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_IS_REVOKED = 3; - static final int INDEX_EXPIRY = 4; + static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; @@ -678,9 +678,6 @@ public class KeyListFragment extends LoaderFragment /** * Bind cursor data to the item list view - * <p/> - * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. - * Thus no ViewHolder is required here. */ @Override public void bindView(View view, Context context, Cursor cursor) { @@ -708,21 +705,20 @@ public class KeyListFragment extends LoaderFragment long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - boolean isExpired = !cursor.isNull(INDEX_EXPIRY) - && new Date(cursor.getLong(INDEX_EXPIRY) * 1000).before(new Date()); + boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; h.mMasterKeyId = masterKeyId; // Note: order is important! if (isRevoked) { - KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, State.REVOKED, R.color.bg_gray); h.mStatus.setVisibility(View.VISIBLE); h.mSlinger.setVisibility(View.GONE); h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); } else if (isExpired) { - KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, State.EXPIRED, R.color.bg_gray); h.mStatus.setVisibility(View.VISIBLE); h.mSlinger.setVisibility(View.GONE); h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); @@ -735,10 +731,10 @@ public class KeyListFragment extends LoaderFragment } else { // this is a public key - show if it's verified if (isVerified) { - KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, KeyFormattingUtils.STATE_VERIFIED); + KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, State.VERIFIED); h.mStatus.setVisibility(View.VISIBLE); } else { - KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, KeyFormattingUtils.STATE_UNVERIFIED); + KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, State.UNVERIFIED); h.mStatus.setVisibility(View.VISIBLE); } h.mSlinger.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index b655a7e55..138f2f4e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -22,9 +22,13 @@ import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; +import android.os.Parcel; import android.support.v4.app.ListFragment; import android.util.TypedValue; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; @@ -33,11 +37,19 @@ 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.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel; import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel; +import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.Iterator; public class LogDisplayFragment extends ListFragment implements OnItemClickListener { @@ -46,6 +58,12 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe OperationResult mResult; public static final String EXTRA_RESULT = "log"; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + } @Override public void onActivityCreated(Bundle savedInstanceState) { @@ -70,6 +88,183 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe getListView().setFastScrollEnabled(true); getListView().setDividerHeight(0); + + } + + @Override + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + inflater.inflate(R.menu.log_display, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_log_display_export_log: + exportLog(); + break; + } + + return super.onOptionsItemSelected(item); + } + + private void exportLog() { + + showExportLogDialog(new File(Constants.Path.APP_DIR, "export.log")); + } + + private void writeToLogFile(final OperationResult.OperationLog operationLog, final File f) { + OperationResult.OperationLog currLog = new OperationResult.OperationLog(); + currLog.add(OperationResult.LogType.MSG_EXPORT_LOG, 0); + + boolean error = false; + + PrintWriter pw = null; + try { + pw = new PrintWriter(f); + pw.print(getPrintableOperationLog(operationLog, "")); + if (pw.checkError()) {//IOException + Log.e(Constants.TAG, "Log Export I/O Exception " + f.getAbsolutePath()); + currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1); + error = true; + } + } catch (FileNotFoundException e) { + Log.e(Constants.TAG, "File not found for exporting log " + f.getAbsolutePath()); + currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN, 1); + error = true; + } + if (pw != null) { + pw.close(); + if (!error && pw.checkError()) {//check if it is only pw.close() which generated error + currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1); + error = true; + } + } + + if (!error) currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_SUCCESS, 1); + + int opResultCode = error ? OperationResult.RESULT_ERROR : OperationResult.RESULT_OK; + OperationResult opResult = new LogExportResult(opResultCode, currLog); + opResult.createNotify(getActivity()).show(); + } + + /** + * returns an indented String of an entire OperationLog + * + * @param opLog log to be converted to indented, printable format + * @param basePadding padding to add at the start of all log entries, made for use with SubLogs + * @return printable, indented version of passed operationLog + */ + private String getPrintableOperationLog(OperationResult.OperationLog opLog, String basePadding) { + String log = ""; + for (Iterator<LogEntryParcel> logIterator = opLog.iterator(); logIterator.hasNext(); ) { + log += getPrintableLogEntry(logIterator.next(), basePadding) + "\n"; + } + log = log.substring(0, log.length() - 1);//gets rid of extra new line + return log; + } + + /** + * returns an indented String of a LogEntryParcel including any sub-logs it may contain + * + * @param entryParcel log entryParcel whose String representation is to be obtained + * @return indented version of passed log entryParcel in a readable format + */ + private String getPrintableLogEntry(OperationResult.LogEntryParcel entryParcel, + String basePadding) { + + final String indent = " ";//4 spaces = 1 Indent level + + String padding = basePadding; + for (int i = 0; i < entryParcel.mIndent; i++) { + padding += indent; + } + String logText = padding; + + switch (entryParcel.mType.mLevel) { + case DEBUG: + logText += "[DEBUG]"; + break; + case INFO: + logText += "[INFO]"; + break; + case WARN: + logText += "[WARN]"; + break; + case ERROR: + logText += "[ERROR]"; + break; + case START: + logText += "[START]"; + break; + case OK: + logText += "[OK]"; + break; + case CANCELLED: + logText += "[CANCELLED]"; + break; + } + + // special case: first parameter may be a quantity + if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0 + && entryParcel.mParameters[0] instanceof Integer) { + logText += getResources().getQuantityString(entryParcel.mType.getMsgId(), + (Integer) entryParcel.mParameters[0], + entryParcel.mParameters); + } else { + logText += getResources().getString(entryParcel.mType.getMsgId(), + entryParcel.mParameters); + } + + if (entryParcel instanceof SubLogEntryParcel) { + OperationResult subResult = ((SubLogEntryParcel) entryParcel).getSubResult(); + LogEntryParcel subEntry = subResult.getLog().getLast(); + if (subEntry != null) { + //the first line of log of subResult is same as entryParcel, so replace logText + logText = getPrintableOperationLog(subResult.getLog(), padding); + } + } + + return logText; + } + + private void showExportLogDialog(final File exportFile) { + + String title = this.getString(R.string.title_export_log); + + String message = this.getString(R.string.specify_file_to_export_log_to); + + FileHelper.saveFile(new FileHelper.FileDialogCallback() { + @Override + public void onFileSelected(File file, boolean checked) { + writeToLogFile(mResult.getLog(), file); + } + }, this.getActivity().getSupportFragmentManager(), title, message, exportFile, null); + } + + private static class LogExportResult extends OperationResult { + + public static Creator<LogExportResult> CREATOR = new Creator<LogExportResult>() { + public LogExportResult createFromParcel(final Parcel source) { + return new LogExportResult(source); + } + + public LogExportResult[] newArray(final int size) { + return new LogExportResult[size]; + } + }; + + public LogExportResult(int result, OperationLog log) { + super(result, log); + } + + /** + * trivial but necessary to implement the Parcelable protocol. + */ + public LogExportResult(Parcel source) { + super(source); + } } @Override @@ -109,7 +304,7 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe mSecondImg = secondImg; } } - + // Check if convertView.setPadding is redundant @Override public View getView(int position, View convertView, ViewGroup parent) { LogEntryParcel entry = getItem(position); @@ -132,7 +327,6 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe if (entry instanceof SubLogEntryParcel) { ih.mSub.setVisibility(View.VISIBLE); convertView.setClickable(false); - convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); OperationResult result = ((SubLogEntryParcel) entry).getSubResult(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index d5ca08936..bb669f6b8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; @@ -149,6 +150,7 @@ public class PassphraseDialogActivity extends FragmentActivity { public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener { private EditText mPassphraseEditText; + private TextView mPassphraseText; private View mInput, mProgress; private CanonicalizedSecretKeyRing mSecretRing = null; @@ -167,7 +169,7 @@ public class PassphraseDialogActivity extends FragmentActivity { // if the dialog is displayed from the application class, design is missing // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay ContextThemeWrapper theme = new ContextThemeWrapper(activity, - R.style.Theme_AppCompat_Light); + R.style.Theme_AppCompat_Light_Dialog); mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID); mServiceIntent = getArguments().getParcelable(EXTRA_DATA); @@ -176,13 +178,30 @@ public class PassphraseDialogActivity extends FragmentActivity { alert.setTitle(R.string.title_unlock); + LayoutInflater inflater = LayoutInflater.from(theme); + View view = inflater.inflate(R.layout.passphrase_dialog, null); + alert.setView(view); + + mPassphraseText = (TextView) view.findViewById(R.id.passphrase_text); + mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase); + mInput = view.findViewById(R.id.input); + mProgress = view.findViewById(R.id.progress); + + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + String userId; CanonicalizedSecretKey.SecretKeyType keyType = CanonicalizedSecretKey.SecretKeyType.PASSPHRASE; + String message; if (mSubKeyId == Constants.key.symmetric || mSubKeyId == Constants.key.none) { - alert.setMessage(R.string.passphrase_for_symmetric_encryption); + message = getString(R.string.passphrase_for_symmetric_encryption); } else { - String message; try { ProviderHelper helper = new ProviderHelper(activity); mSecretRing = helper.getCanonicalizedSecretKeyRing( @@ -191,7 +210,13 @@ public class PassphraseDialogActivity extends FragmentActivity { // above can't be statically verified to have been set in all cases because // the catch clause doesn't return. try { - userId = mSecretRing.getPrimaryUserIdWithFallback(); + String mainUserId = mSecretRing.getPrimaryUserIdWithFallback(); + String[] mainUserIdSplit = KeyRing.splitUserId(mainUserId); + if (mainUserIdSplit[0] != null) { + userId = mainUserIdSplit[0]; + } else { + userId = getString(R.string.user_id_no_name); + } } catch (PgpKeyNotFoundException e) { userId = null; } @@ -228,33 +253,16 @@ public class PassphraseDialogActivity extends FragmentActivity { alert.setCancelable(false); return alert.create(); } - - alert.setMessage(message); } - LayoutInflater inflater = LayoutInflater.from(theme); - View view = inflater.inflate(R.layout.passphrase_dialog, null); - alert.setView(view); - - mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase); - mInput = view.findViewById(R.id.input); - mProgress = view.findViewById(R.id.progress); - - alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - + mPassphraseText.setText(message); if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) { // start pattern dialog and show progress circle here... // Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class); // patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123"); // startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN); - mInput.setVisibility(View.GONE); + mInput.setVisibility(View.INVISIBLE); mProgress.setVisibility(View.VISIBLE); } else { // Hack to open keyboard. @@ -322,7 +330,7 @@ public class PassphraseDialogActivity extends FragmentActivity { return; } - mInput.setVisibility(View.GONE); + mInput.setVisibility(View.INVISIBLE); mProgress.setVisibility(View.VISIBLE); positive.setEnabled(false); @@ -364,7 +372,7 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseEditText.setText(""); mPassphraseEditText.setError(getString(R.string.wrong_passphrase)); mInput.setVisibility(View.VISIBLE); - mProgress.setVisibility(View.GONE); + mProgress.setVisibility(View.INVISIBLE); positive.setEnabled(true); return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java index afec3bf06..65d7eca37 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java @@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import java.util.Vector; @@ -136,7 +137,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T mSearchView.setId(SEARCH_ID); mSearchView.setHint(R.string.menu_search); mSearchView.setCompoundDrawablesWithIntrinsicBounds( - getResources().getDrawable(R.drawable.ic_action_search), null, null, null); + getResources().getDrawable(R.drawable.ic_search_grey_24dp), null, null, null); linearLayout.addView(mSearchView, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); @@ -376,15 +377,15 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T // Check if key is viable for our purposes if (cursor.getInt(mIndexHasEncrypt) == 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, KeyFormattingUtils.STATE_UNAVAILABLE); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNAVAILABLE); enabled = false; } else if (cursor.getInt(mIndexIsVerified) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, KeyFormattingUtils.STATE_VERIFIED); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.VERIFIED); enabled = true; } else { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, KeyFormattingUtils.STATE_UNVERIFIED); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNVERIFIED); enabled = true; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index 53986a392..210960b65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -33,8 +33,6 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import org.spongycastle.bcpg.CompressionAlgorithmTags; -import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; @@ -93,12 +91,6 @@ public class SettingsActivity extends PreferenceActivity { initializePassphraseCacheTtl( (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); - initializeEncryptionAlgorithm( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM)); - - initializeHashAlgorithm( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_HASH_ALGORITHM)); - int[] valueIds = new int[]{ CompressionAlgorithmTags.UNCOMPRESSED, CompressionAlgorithmTags.ZIP, @@ -115,20 +107,6 @@ public class SettingsActivity extends PreferenceActivity { values[i] = "" + valueIds[i]; } - initializeMessageCompression( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION), - entries, values); - - initializeFileCompression( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION), - entries, values); - - initializeAsciiArmor( - (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR)); - - initializeWriteVersionHeader( - (CheckBoxPreference) findPreference(Constants.Pref.WRITE_VERSION_HEADER)); - initializeUseDefaultYubikeyPin( (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN)); @@ -265,12 +243,6 @@ public class SettingsActivity extends PreferenceActivity { initializePassphraseCacheTtl( (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); - initializeEncryptionAlgorithm( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM)); - - initializeHashAlgorithm( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_HASH_ALGORITHM)); - int[] valueIds = new int[]{ CompressionAlgorithmTags.UNCOMPRESSED, CompressionAlgorithmTags.ZIP, @@ -290,20 +262,6 @@ public class SettingsActivity extends PreferenceActivity { values[i] = "" + valueIds[i]; } - initializeMessageCompression( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION), - entries, values); - - initializeFileCompression( - (IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION), - entries, values); - - initializeAsciiArmor( - (CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR)); - - initializeWriteVersionHeader( - (CheckBoxPreference) findPreference(Constants.Pref.WRITE_VERSION_HEADER)); - initializeUseDefaultYubikeyPin( (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN)); @@ -344,113 +302,6 @@ public class SettingsActivity extends PreferenceActivity { }); } - private static void initializeEncryptionAlgorithm(final IntegerListPreference mEncryptionAlgorithm) { - int valueIds[] = {PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, - PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, - PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, - PGPEncryptedData.IDEA,}; - String entries[] = {"AES-128", "AES-192", "AES-256", "Blowfish", "Twofish", "CAST5", - "DES", "Triple DES", "IDEA",}; - String values[] = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mEncryptionAlgorithm.setEntries(entries); - mEncryptionAlgorithm.setEntryValues(values); - mEncryptionAlgorithm.setValue("" + sPreferences.getDefaultEncryptionAlgorithm()); - mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); - mEncryptionAlgorithm - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mEncryptionAlgorithm.setValue(newValue.toString()); - mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); - sPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue - .toString())); - return false; - } - }); - } - - private static void initializeHashAlgorithm(final IntegerListPreference mHashAlgorithm) { - int[] valueIds = new int[]{HashAlgorithmTags.RIPEMD160, - HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, - HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512,}; - String[] entries = new String[]{"RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384", - "SHA-512",}; - String[] values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mHashAlgorithm.setEntries(entries); - mHashAlgorithm.setEntryValues(values); - mHashAlgorithm.setValue("" + sPreferences.getDefaultHashAlgorithm()); - mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); - mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mHashAlgorithm.setValue(newValue.toString()); - mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); - sPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString())); - return false; - } - }); - } - - private static void initializeMessageCompression(final IntegerListPreference mMessageCompression, - String[] entries, String[] values) { - mMessageCompression.setEntries(entries); - mMessageCompression.setEntryValues(values); - mMessageCompression.setValue("" + sPreferences.getDefaultMessageCompression()); - mMessageCompression.setSummary(mMessageCompression.getEntry()); - mMessageCompression - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mMessageCompression.setValue(newValue.toString()); - mMessageCompression.setSummary(mMessageCompression.getEntry()); - sPreferences.setDefaultMessageCompression(Integer.parseInt(newValue - .toString())); - return false; - } - }); - } - - private static void initializeFileCompression - (final IntegerListPreference mFileCompression, String[] entries, String[] values) { - mFileCompression.setEntries(entries); - mFileCompression.setEntryValues(values); - mFileCompression.setValue("" + sPreferences.getDefaultFileCompression()); - mFileCompression.setSummary(mFileCompression.getEntry()); - mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mFileCompression.setValue(newValue.toString()); - mFileCompression.setSummary(mFileCompression.getEntry()); - sPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString())); - return false; - } - }); - } - - private static void initializeAsciiArmor(final CheckBoxPreference mAsciiArmor) { - mAsciiArmor.setChecked(sPreferences.getDefaultAsciiArmor()); - mAsciiArmor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mAsciiArmor.setChecked((Boolean) newValue); - sPreferences.setDefaultAsciiArmor((Boolean) newValue); - return false; - } - }); - } - - private static void initializeWriteVersionHeader(final CheckBoxPreference mWriteVersionHeader) { - mWriteVersionHeader.setChecked(sPreferences.getWriteVersionHeader()); - mWriteVersionHeader.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mWriteVersionHeader.setChecked((Boolean) newValue); - sPreferences.setWriteVersionHeader((Boolean) newValue); - return false; - } - }); - } - private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) { Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs(); mSearchKeyserver.setChecked(prefs.searchKeyserver); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 5a5e1ec36..c94b29bac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -74,16 +74,15 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; -import org.sufficientlysecure.keychain.ui.widget.AspectRatioImageView; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; public class ViewKeyActivity extends BaseActivity implements @@ -107,7 +106,7 @@ public class ViewKeyActivity extends BaseActivity implements private ImageButton mActionEncryptText; private ImageButton mActionNfc; private FloatingActionButton mFab; - private AspectRatioImageView mPhoto; + private ImageView mPhoto; private ImageView mQrCode; private CardView mQrCodeLayout; @@ -123,6 +122,9 @@ public class ViewKeyActivity extends BaseActivity implements private boolean mIsSecret = false; private boolean mHasEncrypt = false; private boolean mIsVerified = false; + private boolean mIsRevoked = false; + private boolean mIsExpired = false; + private MenuItem mRefreshItem; private boolean mIsRefreshing; private Animation mRotate, mRotateSpin; @@ -149,7 +151,7 @@ public class ViewKeyActivity extends BaseActivity implements mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text); mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc); mFab = (FloatingActionButton) findViewById(R.id.fab); - mPhoto = (AspectRatioImageView) findViewById(R.id.view_key_photo); + mPhoto = (ImageView) findViewById(R.id.view_key_photo); mQrCode = (ImageView) findViewById(R.id.view_key_qr_code); mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout); @@ -175,7 +177,7 @@ public class ViewKeyActivity extends BaseActivity implements } }); - mRotate = AnimationUtils.loadAnimation(this, R.anim.rotate); + mRotate = AnimationUtils.loadAnimation(this, R.anim.rotate); mRotate.setRepeatCount(Animation.INFINITE); mRotate.setAnimationListener(new Animation.AnimationListener() { @Override @@ -364,7 +366,7 @@ public class ViewKeyActivity extends BaseActivity implements MenuItem editKey = menu.findItem(R.id.menu_key_view_edit); editKey.setVisible(mIsSecret); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); - certifyFingerprint.setVisible(!mIsSecret && !mIsVerified); + certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); return true; } @@ -415,12 +417,12 @@ public class ViewKeyActivity extends BaseActivity implements private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); - intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{ mMasterKeyId }); + intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId}); startCertifyIntent(intent); } - private void startCertifyIntent (Intent intent) { + private void startCertifyIntent(Intent intent) { // Message is received after signing is done in KeychainIntentService KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this) { public void handleMessage(Message message) { @@ -786,7 +788,7 @@ public class ViewKeyActivity extends BaseActivity implements KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.EXPIRY, + KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.FINGERPRINT, @@ -796,7 +798,7 @@ public class ViewKeyActivity extends BaseActivity implements static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_IS_REVOKED = 3; - static final int INDEX_EXPIRY = 4; + static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; static final int INDEX_FINGERPRINT = 7; @@ -845,9 +847,8 @@ public class ViewKeyActivity extends BaseActivity implements mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; - boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; - boolean isExpired = !data.isNull(INDEX_EXPIRY) - && new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date()); + mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0; + mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0; mIsVerified = data.getInt(INDEX_VERIFIED) > 0; // if the refresh animation isn't playing @@ -857,10 +858,10 @@ public class ViewKeyActivity extends BaseActivity implements // this is done at the end of the animation otherwise } - AsyncTask<String, Void, Bitmap> photoTask = - new AsyncTask<String, Void, Bitmap>() { - protected Bitmap doInBackground(String... fingerprint) { - return ContactHelper.photoFromFingerprint(getContentResolver(), fingerprint[0]); + AsyncTask<Long, Void, Bitmap> photoTask = + new AsyncTask<Long, Void, Bitmap>() { + protected Bitmap doInBackground(Long... mMasterKeyId) { + return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true); } protected void onPostExecute(Bitmap photo) { @@ -871,11 +872,11 @@ public class ViewKeyActivity extends BaseActivity implements // Note: order is important int color; - if (isRevoked) { + if (mIsRevoked) { mStatusText.setText(R.string.view_key_revoked); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - KeyFormattingUtils.STATE_REVOKED, R.color.icons, true); + State.REVOKED, R.color.icons, true); color = getResources().getColor(R.color.android_red_light); mActionEncryptFile.setVisibility(View.GONE); @@ -883,7 +884,7 @@ public class ViewKeyActivity extends BaseActivity implements mActionNfc.setVisibility(View.GONE); mFab.setVisibility(View.GONE); mQrCodeLayout.setVisibility(View.GONE); - } else if (isExpired) { + } else if (mIsExpired) { if (mIsSecret) { mStatusText.setText(R.string.view_key_expired_secret); } else { @@ -891,7 +892,7 @@ public class ViewKeyActivity extends BaseActivity implements } mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - KeyFormattingUtils.STATE_EXPIRED, R.color.icons, true); + State.EXPIRED, R.color.icons, true); color = getResources().getColor(R.color.android_red_light); mActionEncryptFile.setVisibility(View.GONE); @@ -904,10 +905,10 @@ public class ViewKeyActivity extends BaseActivity implements mStatusImage.setVisibility(View.GONE); color = getResources().getColor(R.color.primary); // reload qr code only if the fingerprint changed - if ( !mFingerprint.equals(oldFingerprint)) { + if (!mFingerprint.equals(oldFingerprint)) { loadQrCode(mFingerprint); } - photoTask.execute(mFingerprint); + photoTask.execute(mMasterKeyId); mQrCodeLayout.setVisibility(View.VISIBLE); // and place leftOf qr code @@ -951,16 +952,16 @@ public class ViewKeyActivity extends BaseActivity implements mStatusText.setText(R.string.view_key_verified); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - KeyFormattingUtils.STATE_VERIFIED, R.color.icons, true); + State.VERIFIED, R.color.icons, true); color = getResources().getColor(R.color.primary); - photoTask.execute(mFingerprint); + photoTask.execute(mMasterKeyId); mFab.setVisibility(View.GONE); } else { mStatusText.setText(R.string.view_key_unverified); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, - KeyFormattingUtils.STATE_UNVERIFIED, R.color.icons, true); + State.UNVERIFIED, R.color.icons, true); color = getResources().getColor(R.color.android_orange_light); mFab.setVisibility(View.VISIBLE); @@ -968,27 +969,21 @@ public class ViewKeyActivity extends BaseActivity implements } if (mPreviousColor == 0 || mPreviousColor == color) { - mToolbar.setBackgroundColor(color); mStatusBar.setBackgroundColor(color); mBigToolbar.setBackgroundColor(color); mPreviousColor = color; } else { ObjectAnimator colorFade1 = - ObjectAnimator.ofObject(mToolbar, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, color); - ObjectAnimator colorFade2 = ObjectAnimator.ofObject(mStatusBar, "backgroundColor", new ArgbEvaluator(), mPreviousColor, color); - ObjectAnimator colorFade3 = + ObjectAnimator colorFade2 = ObjectAnimator.ofObject(mBigToolbar, "backgroundColor", new ArgbEvaluator(), mPreviousColor, color); colorFade1.setDuration(1200); colorFade2.setDuration(1200); - colorFade3.setDuration(1200); colorFade1.start(); colorFade2.start(); - colorFade3.start(); mPreviousColor = color; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 9d79b377c..9390e8a69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -43,8 +43,6 @@ import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.Log; -import java.util.Date; - public class ViewKeyAdvActivity extends BaseActivity implements LoaderManager.LoaderCallbacks<Cursor> { @@ -159,7 +157,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.EXPIRY, + KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET }; @@ -167,7 +165,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_IS_REVOKED = 3; - static final int INDEX_EXPIRY = 4; + static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; @@ -212,8 +210,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements boolean isSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; - boolean isExpired = !data.isNull(INDEX_EXPIRY) - && new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date()); + boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; // Note: order is important diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index 8d0a2dd1d..95a6faea9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -260,7 +260,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements static final String[] UNIFIED_PROJECTION = new String[]{ KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET, KeyRings.USER_ID, KeyRings.FINGERPRINT, - KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY, + KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.IS_EXPIRED, }; static final int INDEX_UNIFIED_MASTER_KEY_ID = 1; @@ -270,7 +270,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements static final int INDEX_UNIFIED_ALGORITHM = 5; static final int INDEX_UNIFIED_KEY_SIZE = 6; static final int INDEX_UNIFIED_CREATION = 7; - static final int INDEX_UNIFIED_EXPIRY = 8; + static final int INDEX_UNIFIED_ID_EXPIRED = 8; public Loader<Cursor> onCreateLoader(int id, Bundle args) { setContentShown(false); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index c4e6639a8..7bfebaf62 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -114,12 +114,12 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements static final String[] UNIFIED_PROJECTION = new String[]{ KeyRings._ID, KeyRings.MASTER_KEY_ID, - KeyRings.HAS_ANY_SECRET, KeyRings.IS_REVOKED, KeyRings.EXPIRY, KeyRings.HAS_ENCRYPT + KeyRings.HAS_ANY_SECRET, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, KeyRings.HAS_ENCRYPT }; static final int INDEX_UNIFIED_MASTER_KEY_ID = 1; static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2; static final int INDEX_UNIFIED_IS_REVOKED = 3; - static final int INDEX_UNIFIED_EXPIRY = 4; + static final int INDEX_UNIFIED_IS_EXPIRED = 4; static final int INDEX_UNIFIED_HAS_ENCRYPT = 5; public Loader<Cursor> onCreateLoader(int id, Bundle args) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index 32630b459..628970b27 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -121,7 +121,7 @@ public class ViewKeyFragment extends LoaderFragment implements KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.EXPIRY, + KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.FINGERPRINT, @@ -131,7 +131,7 @@ public class ViewKeyFragment extends LoaderFragment implements static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_IS_REVOKED = 3; - static final int INDEX_EXPIRY = 4; + static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; static final int INDEX_FINGERPRINT = 7; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index 25edc7a02..d22f01a48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -115,12 +115,12 @@ public class ViewKeyTrustFragment extends LoaderFragment implements } static final String[] TRUST_PROJECTION = new String[]{ - KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.IS_REVOKED, KeyRings.EXPIRY, + KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED }; static final int INDEX_TRUST_FINGERPRINT = 1; static final int INDEX_TRUST_IS_REVOKED = 2; - static final int INDEX_TRUST_EXPIRY = 3; + static final int INDEX_TRUST_IS_EXPIRED = 3; static final int INDEX_UNIFIED_HAS_ANY_SECRET = 4; static final int INDEX_VERIFIED = 5; @@ -169,8 +169,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements nothingSpecial = false; } else { - Date expiryDate = new Date(data.getLong(INDEX_TRUST_EXPIRY) * 1000); - if (!data.isNull(INDEX_TRUST_EXPIRY) && expiryDate.before(new Date())) { + if (data.getInt(INDEX_TRUST_IS_EXPIRED) != 0) { // if expired, don’t trust it! message.append(getString(R.string.key_trust_expired)). diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 6ba9e26ad..429feb075 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import java.util.ArrayList; import java.util.HashMap; @@ -175,9 +176,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { } if (entry.isRevoked()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.bg_gray); } else if (entry.isExpired()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.bg_gray); } if (entry.isRevoked() || entry.isExpired()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index a836b35df..226fda20b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -33,6 +33,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; /** @@ -133,11 +134,11 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { boolean enabled; if (cursor.getInt(mIndexIsRevoked) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.REVOKED, R.color.bg_gray); enabled = false; } else if (cursor.getInt(mIndexIsExpiry) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.EXPIRED, R.color.bg_gray); enabled = false; } else { h.statusIcon.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index ff5fbb49a..096dea51f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -272,12 +272,12 @@ public class SubkeysAdapter extends CursorAdapter { PorterDuff.Mode.SRC_IN); if (isRevoked) { - vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24px); + vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp); vStatus.setColorFilter( mContext.getResources().getColor(R.color.bg_gray), PorterDuff.Mode.SRC_IN); } else if (isExpired) { - vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24px); + vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp); vStatus.setColorFilter( mContext.getResources().getColor(R.color.bg_gray), PorterDuff.Mode.SRC_IN); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 6a4f61f4b..3486f1516 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; public class UserIdsAdapter extends UserAttributesAdapter { protected LayoutInflater mInflater; @@ -127,7 +128,7 @@ public class UserIdsAdapter extends UserAttributesAdapter { if (isRevoked) { // set revocation icon (can this even be primary?) - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.bg_gray); // disable revoked user ids vName.setEnabled(false); @@ -149,13 +150,13 @@ public class UserIdsAdapter extends UserAttributesAdapter { int isVerified = cursor.getInt(INDEX_VERIFIED); switch (isVerified) { case Certs.VERIFIED_SECRET: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; case Certs.VERIFIED_SELF: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; default: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_INVALID, KeyFormattingUtils.DEFAULT_COLOR); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); break; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index d5376cbdc..0b1d39fc1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -137,12 +137,10 @@ public class AddSubkeyDialogFragment extends DialogFragment { } }); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - // date picker works based on default time zone - Calendar minDateCal = Calendar.getInstance(TimeZone.getDefault()); - minDateCal.add(Calendar.DAY_OF_YEAR, 1); // at least one day after creation (today) - mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime()); - } + // date picker works based on default time zone + Calendar minDateCal = Calendar.getInstance(TimeZone.getDefault()); + minDateCal.add(Calendar.DAY_OF_YEAR, 1); // at least one day after creation (today) + mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime()); { ArrayList<Choice<Algorithm>> choices = new ArrayList<>(); @@ -283,7 +281,7 @@ public class AddSubkeyDialogFragment extends DialogFragment { // For EC keys, add a curve if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) { curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId(); - // Otherwise, get a keysize + // Otherwise, get a keysize } else { keySize = getProperKeyLength(algorithm, getSelectedKeyLength()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java index 094c4d8a9..ee4af8cbe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java @@ -28,9 +28,6 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Patterns; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -46,11 +43,10 @@ import android.widget.TextView.OnEditorActionListener; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; -import java.util.regex.Matcher; - public class AddUserIdDialogFragment extends DialogFragment implements OnEditorActionListener { private static final String ARG_MESSENGER = "messenger"; private static final String ARG_NAME = "name"; @@ -62,7 +58,7 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA private Messenger mMessenger; private AutoCompleteTextView mName; - private AutoCompleteTextView mEmail; + private EmailEditText mEmail; private EditText mComment; public static AddUserIdDialogFragment newInstance(Messenger messenger, String predefinedName) { @@ -99,38 +95,12 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA alert.setView(view); mName = (AutoCompleteTextView) view.findViewById(R.id.add_user_id_name); - mEmail = (AutoCompleteTextView) view.findViewById(R.id.add_user_id_address); + mEmail = (EmailEditText) view.findViewById(R.id.add_user_id_address); mComment = (EditText) view.findViewById(R.id.add_user_id_comment); mName.setText(predefinedName); - mEmail.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - @Override - public void afterTextChanged(Editable editable) { - String email = editable.toString(); - if (email.length() > 0) { - Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); - if (emailMatcher.matches()) { - mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_ok, 0); - } else { - mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_bad, 0); - } - } else { - // remove drawable if email is empty - mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - } - }); mEmail.setThreshold(1); // Start working from first character mEmail.setAdapter(autoCompleteEmailAdapter); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java index d405b1dda..794af5b15 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + package org.sufficientlysecure.keychain.ui.dialog; import android.app.AlertDialog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index 879e3f6da..07462b4ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -62,12 +62,9 @@ public class DeleteFileDialogFragment extends DialogFragment { CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); - - alert.setIcon(R.drawable.ic_dialog_alert_holo_light); - alert.setTitle(R.string.warning); alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFilename)); - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + alert.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 802f0c11b..32789d53b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -83,8 +83,6 @@ public class DeleteKeyDialogFragment extends DialogFragment { mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage); - builder.setTitle(R.string.warning); - final boolean hasSecret; // If only a single key has been selected @@ -110,12 +108,14 @@ public class DeleteKeyDialogFragment extends DialogFragment { } hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1; - // Set message depending on which key it is. - mMainMessage.setText(getString( - hasSecret ? R.string.secret_key_deletion_confirmation - : R.string.public_key_deletetion_confirmation, - name - )); + if (hasSecret) { + // show title only for secret key deletions, + // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior + builder.setTitle(getString(R.string.title_delete_secret_key, name)); + mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); + } else { + mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); + } } catch (ProviderHelper.NotFoundException e) { dismiss(); return null; @@ -125,7 +125,6 @@ public class DeleteKeyDialogFragment extends DialogFragment { hasSecret = false; } - builder.setIcon(R.drawable.ic_dialog_alert_holo_light); builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java index fc618c9eb..37e05a61d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java @@ -25,11 +25,14 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.DialogFragment; +import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.DatePicker; +import android.widget.LinearLayout; +import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -97,61 +100,64 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { final CheckBox noExpiry = (CheckBox) view.findViewById(R.id.edit_subkey_expiry_no_expiry); final DatePicker datePicker = (DatePicker) view.findViewById(R.id.edit_subkey_expiry_date_picker); + final TextView currentExpiry = (TextView) view.findViewById(R.id.edit_subkey_expiry_current_expiry); + final LinearLayout expiryLayout = (LinearLayout) view.findViewById(R.id.edit_subkey_expiry_layout); noExpiry.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { - datePicker.setVisibility(View.GONE); + expiryLayout.setVisibility(View.GONE); } else { - datePicker.setVisibility(View.VISIBLE); + expiryLayout.setVisibility(View.VISIBLE); } } }); - // init date picker with default selected date if (expiry == 0L) { noExpiry.setChecked(true); - datePicker.setVisibility(View.GONE); - - Calendar todayCal = Calendar.getInstance(TimeZone.getDefault()); - if (creationCal.after(todayCal)) { - // Note: This is just for the rare cases where creation is _after_ today - - // set it to creation date +1 day (don't set it to creationCal, it would break crash - // datePicker.setMinDate() execution with IllegalArgumentException - Calendar creationCalPlusOne = (Calendar) creationCal.clone(); - creationCalPlusOne.add(Calendar.DAY_OF_YEAR, 1); - datePicker.init( - creationCalPlusOne.get(Calendar.YEAR), - creationCalPlusOne.get(Calendar.MONTH), - creationCalPlusOne.get(Calendar.DAY_OF_MONTH), - null - ); - - } else { - // normally, just init with today - datePicker.init( - todayCal.get(Calendar.YEAR), - todayCal.get(Calendar.MONTH), - todayCal.get(Calendar.DAY_OF_MONTH), - null - ); - } + expiryLayout.setVisibility(View.GONE); + + currentExpiry.setText(R.string.btn_no_date); } else { noExpiry.setChecked(false); - datePicker.setVisibility(View.VISIBLE); + expiryLayout.setVisibility(View.VISIBLE); + + // convert from UTC to time zone of device + Calendar expiryCalTimeZone = (Calendar) expiryCal.clone(); + expiryCalTimeZone.setTimeZone(TimeZone.getDefault()); + currentExpiry.setText(DateFormat.getDateFormat( + getActivity()).format(expiryCalTimeZone.getTime())); + } + + // date picker works based on default time zone + Calendar todayCal = Calendar.getInstance(TimeZone.getDefault()); + if (creationCal.after(todayCal)) { + // NOTE: This is just for the rare cases where creation is _after_ today + // Min Date: Creation date + 1 day - // set date picker to current expiry + Calendar creationCalPlusOne = (Calendar) creationCal.clone(); + creationCalPlusOne.add(Calendar.DAY_OF_YEAR, 1); + datePicker.setMinDate(creationCalPlusOne.getTime().getTime()); datePicker.init( - expiryCal.get(Calendar.YEAR), - expiryCal.get(Calendar.MONTH), - expiryCal.get(Calendar.DAY_OF_MONTH), + creationCalPlusOne.get(Calendar.YEAR), + creationCalPlusOne.get(Calendar.MONTH), + creationCalPlusOne.get(Calendar.DAY_OF_MONTH), null ); - } + } else { + // Min Date: today + 1 day - datePicker.setMinDate(creationCal.getTime().getTime()); + // at least one day after creation (today) + todayCal.add(Calendar.DAY_OF_YEAR, 1); + datePicker.setMinDate(todayCal.getTime().getTime()); + datePicker.init( + todayCal.get(Calendar.YEAR), + todayCal.get(Calendar.MONTH), + todayCal.get(Calendar.DAY_OF_MONTH), + null + ); + } alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index a05719072..9e1f21f60 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -43,6 +43,8 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.widget.PasswordEditText; +import org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator.PasswordStrengthView; import org.sufficientlysecure.keychain.util.Log; public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener { @@ -55,9 +57,10 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi public static final String MESSAGE_NEW_PASSPHRASE = "new_passphrase"; private Messenger mMessenger; - private EditText mPassphraseEditText; + private PasswordEditText mPassphraseEditText; private EditText mPassphraseAgainEditText; private CheckBox mNoPassphraseCheckBox; + private PasswordStrengthView mPassphraseStrengthView; /** * Creates new instance of this dialog fragment @@ -92,15 +95,17 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); alert.setTitle(title); - alert.setMessage(R.string.enter_passphrase_twice); LayoutInflater inflater = activity.getLayoutInflater(); View view = inflater.inflate(R.layout.passphrase_repeat_dialog, null); alert.setView(view); - mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase); + mPassphraseEditText = (PasswordEditText) view.findViewById(R.id.passphrase_passphrase); mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again); mNoPassphraseCheckBox = (CheckBox) view.findViewById(R.id.passphrase_no_passphrase); + mPassphraseStrengthView = (PasswordStrengthView) view.findViewById(R.id.passphrase_repeat_passphrase_strength); + mPassphraseEditText.setPasswordStrengthView(mPassphraseStrengthView); + if (TextUtils.isEmpty(oldPassphrase)) { mNoPassphraseCheckBox.setChecked(true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 38ed88b9c..c5403e054 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -379,27 +379,29 @@ public class KeyFormattingUtils { public static final int DEFAULT_COLOR = -1; - public static final int STATE_REVOKED = 1; - public static final int STATE_EXPIRED = 2; - public static final int STATE_VERIFIED = 3; - public static final int STATE_UNAVAILABLE = 4; - public static final int STATE_ENCRYPTED = 5; - public static final int STATE_NOT_ENCRYPTED = 6; - public static final int STATE_UNVERIFIED = 7; - public static final int STATE_UNKNOWN_KEY = 8; - public static final int STATE_INVALID = 9; - public static final int STATE_NOT_SIGNED = 10; - - public static void setStatusImage(Context context, ImageView statusIcon, int state) { + public static enum State { + REVOKED, + EXPIRED, + VERIFIED, + UNAVAILABLE, + ENCRYPTED, + NOT_ENCRYPTED, + UNVERIFIED, + UNKNOWN_KEY, + INVALID, + NOT_SIGNED + } + + public static void setStatusImage(Context context, ImageView statusIcon, State state) { setStatusImage(context, statusIcon, null, state); } - public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, int state) { + public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, State state) { setStatusImage(context, statusIcon, statusText, state, KeyFormattingUtils.DEFAULT_COLOR, false); } public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, - int state, int color) { + State state, int color) { setStatusImage(context, statusIcon, statusText, state, color, false); } @@ -407,16 +409,16 @@ public class KeyFormattingUtils { * Sets status image based on constant */ public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, - int state, int color, boolean big) { + State state, int color, boolean big) { switch (state) { /** GREEN: everything is good **/ - case STATE_VERIFIED: { + case VERIFIED: { if (big) { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_96px)); + context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_96dp)); } else { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_green_light; @@ -428,9 +430,9 @@ public class KeyFormattingUtils { } break; } - case STATE_ENCRYPTED: { + case ENCRYPTED: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_lock_closed_24px)); + context.getResources().getDrawable(R.drawable.status_lock_closed_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_green_light; } @@ -442,13 +444,13 @@ public class KeyFormattingUtils { break; } /** ORANGE: mostly bad... **/ - case STATE_UNVERIFIED: { + case UNVERIFIED: { if (big) { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_96px)); + context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_96dp)); } else { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_orange_light; @@ -460,9 +462,9 @@ public class KeyFormattingUtils { } break; } - case STATE_UNKNOWN_KEY: { + case UNKNOWN_KEY: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_orange_light; } @@ -474,13 +476,13 @@ public class KeyFormattingUtils { break; } /** RED: really bad... **/ - case STATE_REVOKED: { + case REVOKED: { if (big) { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_96px)); + context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_96dp)); } else { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_red_light; @@ -492,13 +494,13 @@ public class KeyFormattingUtils { } break; } - case STATE_EXPIRED: { + case EXPIRED: { if (big) { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_96px)); + context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_96dp)); } else { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_red_light; @@ -510,9 +512,9 @@ public class KeyFormattingUtils { } break; } - case STATE_NOT_ENCRYPTED: { + case NOT_ENCRYPTED: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_lock_open_24px)); + context.getResources().getDrawable(R.drawable.status_lock_open_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_red_light; } @@ -523,9 +525,9 @@ public class KeyFormattingUtils { } break; } - case STATE_NOT_SIGNED: { + case NOT_SIGNED: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_red_light; } @@ -536,9 +538,9 @@ public class KeyFormattingUtils { } break; } - case STATE_INVALID: { + case INVALID: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.android_red_light; } @@ -550,9 +552,9 @@ public class KeyFormattingUtils { break; } /** special **/ - case STATE_UNAVAILABLE: { + case UNAVAILABLE: { statusIcon.setImageDrawable( - context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24px)); + context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { color = R.color.bg_gray; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 6d0e6556f..fc912fccb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; public class CertifyKeySpinner extends KeySpinner { private long mHiddenMasterKeyId = Constants.key.none; @@ -103,16 +104,16 @@ public class CertifyKeySpinner extends KeySpinner { @Override boolean setStatus(Context context, Cursor cursor, ImageView statusView) { if (cursor.getInt(mIndexIsRevoked) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray); return false; } if (cursor.getInt(mIndexIsExpired) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray); return false; } // don't invalidate the "None" entry, which is also null! if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray); return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java new file mode 100644 index 000000000..697f5a61e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.Patterns; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.ContactHelper; + +import java.util.regex.Matcher; + +public class EmailEditText extends AutoCompleteTextView { + EmailEditText emailEditText; + + public EmailEditText(Context context) { + super(context); + emailEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + this.addTextChangedListener(textWatcher); + } + + public EmailEditText(Context context, AttributeSet attrs) { + super(context, attrs); + emailEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + this.addTextChangedListener(textWatcher); + } + + public EmailEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + emailEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + this.addTextChangedListener(textWatcher); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public EmailEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + emailEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + this.addTextChangedListener(textWatcher); + } + + TextWatcher textWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable editable) { + String email = editable.toString(); + if (email.length() > 0) { + Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); + if (emailMatcher.matches()) { + emailEditText.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_ok, 0); + } else { + emailEditText.setCompoundDrawablesWithIntrinsicBounds(0, 0, + R.drawable.uid_mail_bad, 0); + } + } else { + // remove drawable if email is empty + emailEditText.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + }; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index f05f5f96b..d20e2bc99 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -90,7 +90,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView { } private void setImageByKey(ImageView view, EncryptionKey key) { - Bitmap photo = ContactHelper.photoFromFingerprint(getContext().getContentResolver(), key.getFingerprint()); + Bitmap photo = ContactHelper.getCachedPhotoByMasterKeyId(getContext().getContentResolver(), key.getKeyId()); if (photo != null) { view.setImageBitmap(photo); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordEditText.java new file mode 100644 index 000000000..04c48922b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordEditText.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator.PasswordStrengthView; + +/** + * Developer: chipset + * Package : org.sufficientlysecure.keychain.layouts + * Project : open-keychain + * Date : 6/3/15 + */ +public class PasswordEditText extends EditText { + + PasswordEditText passwordEditText; + PasswordStrengthView passwordStrengthView; + + public PasswordEditText(Context context) { + super(context); + passwordEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + this.addTextChangedListener(textWatcher); + } + + public PasswordEditText(Context context, AttributeSet attrs) { + super(context, attrs); + passwordEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + this.addTextChangedListener(textWatcher); + } + + public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + passwordEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + this.addTextChangedListener(textWatcher); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + passwordEditText = this; + this.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + this.addTextChangedListener(textWatcher); + } + + + TextWatcher textWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable editable) { + String passphrase = editable.toString(); + passwordStrengthView.setPassword(passphrase); + } + }; + +// public PasswordStrengthView getPasswordStrengthView() { +// return passwordStrengthView; +// } + + public void setPasswordStrengthView(PasswordStrengthView mPasswordStrengthView) { + this.passwordStrengthView = mPasswordStrengthView; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index fe91e306e..10327a6a4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -29,6 +29,7 @@ import android.widget.ImageView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; public class SignKeySpinner extends KeySpinner { public SignKeySpinner(Context context) { @@ -84,15 +85,15 @@ public class SignKeySpinner extends KeySpinner { @Override boolean setStatus(Context context, Cursor cursor, ImageView statusView) { if (cursor.getInt(mIndexIsRevoked) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray); return false; } if (cursor.getInt(mIndexIsExpired) != 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray); return false; } if (cursor.getInt(mIndexHasSign) == 0) { - KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray); return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/passwordstrengthindicator/PasswordStrengthBarView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/passwordstrengthindicator/PasswordStrengthBarView.java new file mode 100644 index 000000000..9e06c4cce --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/passwordstrengthindicator/PasswordStrengthBarView.java @@ -0,0 +1,117 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Matt Allen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.util.AttributeSet; + +/** + * Created by matt on 04/07/2014. + * https://github.com/matt-allen/android-password-strength-indicator + */ +public class PasswordStrengthBarView extends PasswordStrengthView { + + public PasswordStrengthBarView(Context context, AttributeSet attrs) { + super(context, attrs); + mMinHeight = 80; + mMinWidth = 300; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + generateIndicatorColor(); + // Default to full width + int indWidth = mIndicatorWidth; + // If score, leave it as full - can cause it to become + // less than full width in this calculation + if (mCurrentScore < 20) indWidth = (mIndicatorWidth / 20) * mCurrentScore; + // Draw indicator + canvas.drawRect( + getPaddingLeft(), + getPaddingTop(), + indWidth, + mIndicatorHeight, + mIndicatorPaint + ); + // Draw guides if true + if (mShowGuides) { + // TODO: Try and do this with a loop, for efficiency + // Draw bottom guide border + float positionY = getHeight() - getPaddingBottom() - getPaddingTop(); + float notchHeight = (float) (positionY * 0.8); + canvas.drawLine( + getPaddingLeft(), + positionY, + getWidth() - getPaddingRight(), + positionY, + mGuidePaint); + // Show left-most notch + canvas.drawLine( + getPaddingLeft(), + positionY, + getPaddingLeft(), + notchHeight, + mGuidePaint + ); + // Show middle-left notch + canvas.drawLine( + (float) (mIndicatorWidth * 0.25) + getPaddingLeft(), + positionY, + (float) (mIndicatorWidth * 0.25) + getPaddingLeft(), + notchHeight, + mGuidePaint + ); + // Show the middle notch + canvas.drawLine( + (float) (mIndicatorWidth * 0.5) + getPaddingLeft(), + positionY, + (float) (mIndicatorWidth * 0.5) + getPaddingLeft(), + notchHeight, + mGuidePaint + ); + // Show the middle-right notch + canvas.drawLine( + (float) (mIndicatorWidth * 0.75) + getPaddingLeft(), + positionY, + (float) (mIndicatorWidth * 0.75) + getPaddingLeft(), + notchHeight, + mGuidePaint + ); + // Show the right-most notch + canvas.drawLine( + mIndicatorWidth + getPaddingLeft(), + positionY, + mIndicatorWidth + getPaddingLeft(), + notchHeight, + mGuidePaint + ); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/passwordstrengthindicator/PasswordStrengthView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/passwordstrengthindicator/PasswordStrengthView.java new file mode 100644 index 000000000..d7270ff58 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/passwordstrengthindicator/PasswordStrengthView.java @@ -0,0 +1,360 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Matt Allen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import org.sufficientlysecure.keychain.R; + +/** + * Created by Matt Allen + * 01/07/14 + * http://www.mattallensoftware.co.uk + * mattallen092@gmail.com + * <p/> + * https://github.com/matt-allen/android-password-strength-indicator + * <p/> + * <p> + * This View is designed to indicate how secure a user-entered password is in a visual way to + * relay to the user if they need to make it stronger. The strength of the password can be set + * at creation (or after) which will decide whether their password is strong enough. + * </p> + * <p/> + * <p> + * The password strength is decided by an index of 20. The minimum score needed to pass is 10 + * which means the String has met the conditions imposed by the strength test, but can be improved. + * If the password scores 10-19 it is considered weak, and only if it scores 20 will it be + * considered strong. + * </p> + */ +public class PasswordStrengthView extends View { + + protected static final int COLOR_FAIL = Color.parseColor("#e74c3c"); + protected static final int COLOR_WEAK = Color.parseColor("#e67e22"); + protected static final int COLOR_STRONG = Color.parseColor("#2ecc71"); + + protected int mMinWidth; + protected int mMinHeight; + + protected Paint mIndicatorPaint; + protected Paint mGuidePaint; + + protected int mIndicatorHeight; + protected int mIndicatorWidth; + protected int mCurrentScore; + + protected int mColorFail; + protected int mColorWeak; + protected int mColorStrong; + + protected boolean mShowGuides = true; + + /** + * Used to define that the indicator should only be looking + * for a weak password. The bare minimum is used here to let + * the user continue. + */ + public static final int STRENGTH_WEAK = 0; + + /** + * A fairly strict rule for generating a password. It encourages a password that is + * less easy to crack. + */ + public static final int STRENGTH_MEDIUM = 1; + + /** + * A strong algorithm that encourages very strong passwords that should be fairly long, with + * non-alphanumeric, numbers, and upper case. + */ + public static final int STRENGTH_STRONG = 2; + + private int mStrengthRequirement = -1; + protected String mPassword; + + public PasswordStrengthView(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray style = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.PasswordStrengthView, + 0, 0); + + try { + mStrengthRequirement = style.getInteger(R.styleable.PasswordStrengthView_strength, + STRENGTH_MEDIUM); + mShowGuides = style.getBoolean(R.styleable.PasswordStrengthView_showGuides, true); + mColorFail = style.getColor(R.styleable.PasswordStrengthView_color_fail, COLOR_FAIL); + mColorWeak = style.getColor(R.styleable.PasswordStrengthView_color_weak, COLOR_WEAK); + mColorStrong = style.getColor(R.styleable.PasswordStrengthView_color_strong, + COLOR_STRONG); + } catch (Exception e) { + e.printStackTrace(); + } + // Create and style the paint used for drawing the guide on the indicator + mGuidePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGuidePaint.setStyle(Paint.Style.FILL_AND_STROKE); + mGuidePaint.setColor(Color.BLACK); + // Create and style paint for indicator + mIndicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mIndicatorPaint.setStyle(Paint.Style.FILL); + } + + /** + * This view can determine if the password entered by the user is acceptable for + * use by your use case. This is based on the strength requirement you have set. + * + * @return True if requirement has been met + */ + public boolean isStrengthRequirementMet() { + return (mCurrentScore >= 10); + } + + /** + * Change the strength requirement of the password entered by the user. This will also + * re-check the password already entered against these new requirements. + * + * @param requiredStrength Use the public constants of this class to set + */ + public void setStrengthRequirement(int requiredStrength) { + if (requiredStrength >= 0 && requiredStrength <= 2) { + mStrengthRequirement = requiredStrength; + if (mPassword != null && mPassword.length() > 0) { + generatePasswordScore(); + // Update view with new score + invalidate(); + requestLayout(); + } + } else { + throw new IndexOutOfBoundsException("Input out of expected range"); + } + } + + /** + * Update the password string to check strength of + * + * @param passwordString String representation of user-input + */ + public void setPassword(String passwordString) { + if (passwordString != null && passwordString.length() > 0) { + mPassword = passwordString; + generatePasswordScore(); + } else { + mPassword = ""; + mCurrentScore = 0; + } + + // Update view with new score + invalidate(); + requestLayout(); + } + + /** + * Private convenience method for adding to the password score + * + * @param score Amount to be added to current score + */ + protected void addToPasswordScore(int score) { + int newScore = mCurrentScore + score; + + // Limit max score + if (newScore > 20) { + mCurrentScore = 20; + } else { + mCurrentScore = newScore; + } + } + + /** + * Call this to determine the current strength requirement set on the algorithm + * + * @return Int representation of the current strength set for the indicator + */ + public int getStrengthRequirement() { + return mStrengthRequirement; + } + + /** + * Generate a score based on the password. The password will already need to be stored + * as a class member before running this. + */ + protected void generatePasswordScore() { + mCurrentScore = 0; + int upperCase = getUppercaseCount(mPassword); + int nonAlpha = getNonAlphanumericCount(mPassword); + int numbers = getNumberCount(mPassword); + switch (mStrengthRequirement) { + case STRENGTH_WEAK: + addToPasswordScore(mPassword.length() * 2); + addToPasswordScore(upperCase * 2); + addToPasswordScore(nonAlpha * 2); + addToPasswordScore(numbers * 2); + break; + + case STRENGTH_MEDIUM: + addToPasswordScore(mPassword.length()); + addToPasswordScore(upperCase); + addToPasswordScore(nonAlpha * 2); + addToPasswordScore(numbers); + break; + + case STRENGTH_STRONG: + addToPasswordScore(mPassword.length() / 2); + // Cut the score in half to make this a very high requirement + addToPasswordScore(upperCase); + addToPasswordScore(nonAlpha); + addToPasswordScore(numbers); + break; + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldW, int oldH) { + super.onSizeChanged(w, h, oldW, oldH); + int paddingX = getPaddingLeft(); + int paddingY = getPaddingTop(); + mIndicatorHeight = h - paddingY; + mIndicatorWidth = w - paddingX; + } + + /** + * The standard parts of the onMeasure needed to create the password strength + * indicator. Subclasses should call super.onMeasure, but also need to set + * the minimum height and width in the constructor. + * + * @param widthMeasureSpec The measurement given by the system + * @param heightMeasureSpec The measurement given by the system + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Set minimum space for the view to do it's thing + int minW = getPaddingLeft() + getPaddingRight() + mMinWidth; + int w = resolveSizeAndState(minW, widthMeasureSpec, 1); + // And give it enough height so it's visible + int minH = mMinHeight + getPaddingBottom() + getPaddingTop(); + int h = resolveSizeAndState(minH, heightMeasureSpec, 0); + // Feed these back into UIKit + setMeasuredDimension(w, h); + } + + /** + * Set the colour of the indicator {@code Paint} to one that is appropriate + * for the strength of the password. + */ + protected void generateIndicatorColor() { + int color = mColorFail; + if (mCurrentScore >= 18) { + color = mColorStrong; + } else if (mCurrentScore >= 10) { + color = mColorWeak; + } + mIndicatorPaint.setColor(color); + } + + /** + * Quick method to determine how many of the characters in a given string are upper case + * + * @param stringToCheck The string to examine + * @return Number of upper case characters + */ + protected int getUppercaseCount(String stringToCheck) { + int score = 0; + int loops = stringToCheck.length() - 1; + for (int i = 0; i <= loops; i++) { + if (Character.isUpperCase(stringToCheck.charAt(i))) { + score++; + } + } + return score; + } + + /** + * A convenience method to determine how many characters in the given String aren't + * letters or numbers. + * + * @param stringToCheck + * @return Number of characters that aren't numbers or letters + */ + protected int getNonAlphanumericCount(String stringToCheck) { + int score = 0; + int loops = stringToCheck.length() - 1; + for (int i = 0; i <= loops; i++) { + if (!Character.isLetter(stringToCheck.charAt(i)) && + !Character.isDigit(stringToCheck.charAt(i))) { + score++; + } + } + return score; + } + + /** + * A convenience method for returning the count of numbers in a given String. + * + * @param stringToCheck + * @return The numbers of digits in the String + */ + protected int getNumberCount(String stringToCheck) { + int score = 0; + int loops = stringToCheck.length() - 1; + for (int i = 0; i <= loops; i++) { + if (Character.isDigit(stringToCheck.charAt(i))) { + score++; + } + } + return score; + } + + /** + * Set the guides to show on the view.<br /> + * On the line style, the guides will show underneath<br /> + * On the rounded style, the guides will be shown on the outer edges.<br /> + * The view will be redrawn after the method is called. + * + * @param showGuides True if you want the guides to be shown + */ + public void setShowGuides(boolean showGuides) { + mShowGuides = showGuides; + if (mPassword != null && mPassword.length() > 0) { + generatePasswordScore(); + } else { + mCurrentScore = 0; + } + + invalidate(); + requestLayout(); + } + + /** + * Determine whether the view is showing the guides for the password score + * + * @return True if the guides are being shown + */ + public boolean isShowingGuides() { + return mShowGuides; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 28480cee5..c66dc04d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -20,18 +20,15 @@ package org.sufficientlysecure.keychain.util; import android.accounts.Account; import android.accounts.AccountManager; import android.annotation.TargetApi; -import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; -import android.os.RemoteException; import android.provider.ContactsContract; import android.util.Patterns; @@ -44,7 +41,6 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import java.io.InputStream; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -53,40 +49,7 @@ import java.util.Set; public class ContactHelper { - public static final String[] KEYS_TO_CONTACT_PROJECTION = new String[]{ - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.FINGERPRINT, - KeychainContract.KeyRings.KEY_ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.EXPIRY, - KeychainContract.KeyRings.IS_REVOKED}; - - public static final int INDEX_USER_ID = 0; - public static final int INDEX_FINGERPRINT = 1; - public static final int INDEX_KEY_ID = 2; - public static final int INDEX_MASTER_KEY_ID = 3; - public static final int INDEX_EXPIRY = 4; - public static final int INDEX_IS_REVOKED = 5; - - public static final String[] USER_IDS_PROJECTION = new String[]{ - UserPackets.USER_ID - }; - - public static final int INDEX_USER_IDS_USER_ID = 0; - - public static final String NON_REVOKED_SELECTION = UserPackets.IS_REVOKED + "=0"; - - public static final String[] ID_PROJECTION = new String[]{ContactsContract.RawContacts._ID}; - public static final String[] SOURCE_ID_PROJECTION = new String[]{ContactsContract.RawContacts.SOURCE_ID}; - - public static final String ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION = - ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?"; - public static final String ACCOUNT_TYPE_SELECTION = ContactsContract.RawContacts.ACCOUNT_TYPE + "=?"; - public static final String RAW_CONTACT_AND_MIMETYPE_SELECTION = - ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?"; - public static final String ID_SELECTION = ContactsContract.RawContacts._ID + "=?"; - - private static final Map<String, Bitmap> photoCache = new HashMap<>(); + private static final Map<Long, Bitmap> photoCache = new HashMap<>(); public static List<String> getPossibleUserEmails(Context context) { Set<String> accountMails = getAccountEmails(context); @@ -282,27 +245,30 @@ public class ContactHelper { return null; } - public static Bitmap photoFromFingerprint(ContentResolver contentResolver, String fingerprint) { - if (fingerprint == null) { + public static Bitmap getCachedPhotoByMasterKeyId(ContentResolver contentResolver, long masterKeyId) { + if (masterKeyId == -1) { return null; } - if (!photoCache.containsKey(fingerprint)) { - photoCache.put(fingerprint, loadPhotoFromFingerprint(contentResolver, fingerprint)); + if (!photoCache.containsKey(masterKeyId)) { + photoCache.put(masterKeyId, loadPhotoByMasterKeyId(contentResolver, masterKeyId, false)); } - return photoCache.get(fingerprint); + return photoCache.get(masterKeyId); } - private static Bitmap loadPhotoFromFingerprint(ContentResolver contentResolver, String fingerprint) { - if (fingerprint == null) return null; + public static Bitmap loadPhotoByMasterKeyId(ContentResolver contentResolver, long masterKeyId, + boolean highRes) { + if (masterKeyId == -1) { + return null; + } try { - int rawContactId = findRawContactId(contentResolver, fingerprint); + long rawContactId = findRawContactId(contentResolver, masterKeyId); if (rawContactId == -1) { return null; } Uri rawContactUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId); Uri contactUri = ContactsContract.RawContacts.getContactLookupUri(contentResolver, rawContactUri); InputStream photoInputStream = - ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri); + ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri, highRes); if (photoInputStream == null) { return null; } @@ -312,12 +278,27 @@ public class ContactHelper { } } + public static final String[] KEYS_TO_CONTACT_PROJECTION = new String[]{ + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_EXPIRED, + KeychainContract.KeyRings.IS_REVOKED}; + + public static final int INDEX_MASTER_KEY_ID = 0; + public static final int INDEX_USER_ID = 1; + public static final int INDEX_IS_EXPIRED = 2; + public static final int INDEX_IS_REVOKED = 3; + /** - * Write the current Keychain to the contact db + * Write/Update the current OpenKeychain keys to the contact db */ public static void writeKeysToContacts(Context context) { ContentResolver resolver = context.getContentResolver(); - Set<String> deletedKeys = getRawContactFingerprints(resolver); + Set<Long> deletedKeys = getRawContactMasterKeyIds(resolver); + + if (Constants.DEBUG_SYNC_REMOVE_CONTACTS) { + debugDeleteRawContacts(resolver); + } // ContentProviderClient client = resolver.acquireContentProviderClient(ContactsContract.AUTHORITY_URI); // ContentValues values = new ContentValues(); @@ -336,42 +317,41 @@ public class ContactHelper { null, null, null); if (cursor != null) { while (cursor.moveToNext()) { - String[] primaryUserId = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(cursor.getBlob(INDEX_FINGERPRINT)); - deletedKeys.remove(fingerprint); - - Log.d(Constants.TAG, "fingerprint: " + fingerprint); - - String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(cursor.getLong(INDEX_KEY_ID)); long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - boolean isExpired = !cursor.isNull(INDEX_EXPIRY) - && new Date(cursor.getLong(INDEX_EXPIRY) * 1000).before(new Date()); + String[] userIdSplit = KeyRing.splitUserId(cursor.getString(INDEX_USER_ID)); + String keyIdShort = KeyFormattingUtils.convertKeyIdToHexShort(cursor.getLong(INDEX_MASTER_KEY_ID)); + boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - int rawContactId = findRawContactId(resolver, fingerprint); - ArrayList<ContentProviderOperation> ops = new ArrayList<>(); - Log.d(Constants.TAG, "raw contact id: " + rawContactId); + Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); + + deletedKeys.remove(masterKeyId); + + // get raw contact to this master key id + long rawContactId = findRawContactId(resolver, masterKeyId); + Log.d(Constants.TAG, "rawContactId: " + rawContactId); + + ArrayList<ContentProviderOperation> ops = new ArrayList<>(); // Do not store expired or revoked keys in contact db - and remove them if they already exist if (isExpired || isRevoked) { Log.d(Constants.TAG, "Expired or revoked: Deleting " + rawContactId); if (rawContactId != -1) { - resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ID_SELECTION, - new String[]{Integer.toString(rawContactId)}); + deleteRawContactById(resolver, rawContactId); } - } else if (primaryUserId[0] != null) { + } else if (userIdSplit[0] != null) { // Create a new rawcontact with corresponding key if it does not exist yet if (rawContactId == -1) { - Log.d(Constants.TAG, "Insert new raw contact with fingerprint " + fingerprint); + Log.d(Constants.TAG, "Insert new raw contact with masterKeyId " + masterKeyId); - insertContact(ops, context, fingerprint); + insertContact(ops, context, masterKeyId); writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort); } // We always update the display name (which is derived from primary user id) // and email addresses from user id - writeContactDisplayName(ops, rawContactId, primaryUserId[0]); + writeContactDisplayName(ops, rawContactId, userIdSplit[0]); writeContactEmail(ops, resolver, rawContactId, masterKeyId); try { resolver.applyBatch(ContactsContract.AUTHORITY, ops); @@ -383,42 +363,84 @@ public class ContactHelper { cursor.close(); } - // Delete fingerprints that are no longer present in OK - for (String fingerprint : deletedKeys) { - resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, - new String[]{Constants.ACCOUNT_TYPE, fingerprint}); + // Delete master key ids that are no longer present in OK + for (Long masterKeyId : deletedKeys) { + Log.d(Constants.TAG, "Delete raw contact with masterKeyId " + masterKeyId); + deleteRawContactByMasterKeyId(resolver, masterKeyId); } } /** - * @return a set of all key fingerprints currently present in the contact db + * Delete all raw contacts associated to OpenKeychain. + * <p/> + * TODO: Does this work? + */ + private static int debugDeleteRawContacts(ContentResolver resolver) { + Log.d(Constants.TAG, "Deleting all raw contacts associated to OK..."); + return resolver.delete(ContactsContract.RawContacts.CONTENT_URI, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", + new String[]{ + Constants.ACCOUNT_TYPE + }); + } + + private static int deleteRawContactById(ContentResolver resolver, long rawContactId) { + return resolver.delete(ContactsContract.RawContacts.CONTENT_URI, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts._ID + "=?", + new String[]{ + Constants.ACCOUNT_TYPE, Long.toString(rawContactId) + }); + } + + private static int deleteRawContactByMasterKeyId(ContentResolver resolver, long masterKeyId) { + return resolver.delete(ContactsContract.RawContacts.CONTENT_URI, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?", + new String[]{ + Constants.ACCOUNT_TYPE, Long.toString(masterKeyId) + }); + } + + /** + * @return a set of all key master key ids currently present in the contact db */ - private static Set<String> getRawContactFingerprints(ContentResolver resolver) { - HashSet<String> result = new HashSet<>(); - Cursor fingerprints = resolver.query(ContactsContract.RawContacts.CONTENT_URI, SOURCE_ID_PROJECTION, - ACCOUNT_TYPE_SELECTION, new String[]{Constants.ACCOUNT_TYPE}, null); - if (fingerprints != null) { - while (fingerprints.moveToNext()) { - result.add(fingerprints.getString(0)); + private static Set<Long> getRawContactMasterKeyIds(ContentResolver resolver) { + HashSet<Long> result = new HashSet<>(); + Cursor masterKeyIds = resolver.query(ContactsContract.RawContacts.CONTENT_URI, + new String[]{ + ContactsContract.RawContacts.SOURCE_ID + }, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?", + new String[]{ + Constants.ACCOUNT_TYPE + }, null); + if (masterKeyIds != null) { + while (masterKeyIds.moveToNext()) { + result.add(masterKeyIds.getLong(0)); } - fingerprints.close(); + masterKeyIds.close(); } return result; } /** - * This will search the contact db for a raw contact with a given fingerprint + * This will search the contact db for a raw contact with a given master key id * * @return raw contact id or -1 if not found */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private static int findRawContactId(ContentResolver resolver, String fingerprint) { - int rawContactId = -1; - Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, ID_PROJECTION, - ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, new String[]{Constants.ACCOUNT_TYPE, fingerprint}, null, null); + private static long findRawContactId(ContentResolver resolver, long masterKeyId) { + long rawContactId = -1; + Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, + new String[]{ + ContactsContract.RawContacts._ID + }, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?", + new String[]{ + Constants.ACCOUNT_TYPE, Long.toString(masterKeyId) + }, null, null); if (raw != null) { if (raw.moveToNext()) { - rawContactId = raw.getInt(0); + rawContactId = raw.getLong(0); } raw.close(); } @@ -426,13 +448,13 @@ public class ContactHelper { } /** - * Creates a empty raw contact with a given fingerprint + * Creates a empty raw contact with a given masterKeyId */ - private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, String fingerprint) { + private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, long masterKeyId) { ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE) - .withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint) + .withValue(ContactsContract.RawContacts.SOURCE_ID, Long.toString(masterKeyId)) .build()); } @@ -441,7 +463,7 @@ public class ContactHelper { * <p/> * This creates the link to OK in contact details */ - private static void writeContactKey(ArrayList<ContentProviderOperation> ops, Context context, int rawContactId, + private static void writeContactKey(ArrayList<ContentProviderOperation> ops, Context context, long rawContactId, long masterKeyId, String keyIdShort) { ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId) .withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE) @@ -454,16 +476,22 @@ public class ContactHelper { * Write all known email addresses of a key (derived from user ids) to a given raw contact */ private static void writeContactEmail(ArrayList<ContentProviderOperation> ops, ContentResolver resolver, - int rawContactId, long masterKeyId) { - ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), + long rawContactId, long masterKeyId) { + ops.add(selectByRawContactAndItemType( + ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build()); Cursor ids = resolver.query(UserPackets.buildUserIdsUri(masterKeyId), - USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null); + new String[]{ + UserPackets.USER_ID + }, + UserPackets.IS_REVOKED + "=0", + null, null); if (ids != null) { while (ids.moveToNext()) { String[] userId = KeyRing.splitUserId(ids.getString(0)); if (userId[1] != null) { - ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), + ops.add(referenceRawContact( + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) @@ -475,7 +503,7 @@ public class ContactHelper { } } - private static void writeContactDisplayName(ArrayList<ContentProviderOperation> ops, int rawContactId, + private static void writeContactDisplayName(ArrayList<ContentProviderOperation> ops, long rawContactId, String displayName) { if (displayName != null) { ops.add(insertOrUpdateForRawContact(ContactsContract.Data.CONTENT_URI, rawContactId, @@ -486,13 +514,13 @@ public class ContactHelper { } private static ContentProviderOperation.Builder referenceRawContact(ContentProviderOperation.Builder builder, - int rawContactId) { + long rawContactId) { return rawContactId == -1 ? builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) : builder.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId); } - private static ContentProviderOperation.Builder insertOrUpdateForRawContact(Uri uri, int rawContactId, + private static ContentProviderOperation.Builder insertOrUpdateForRawContact(Uri uri, long rawContactId, String itemType) { if (rawContactId == -1) { return referenceRawContact(ContentProviderOperation.newInsert(uri), rawContactId).withValue( @@ -503,8 +531,11 @@ public class ContactHelper { } private static ContentProviderOperation.Builder selectByRawContactAndItemType( - ContentProviderOperation.Builder builder, int rawContactId, String itemType) { - return builder.withSelection(RAW_CONTACT_AND_MIMETYPE_SELECTION, - new String[]{Integer.toString(rawContactId), itemType}); + ContentProviderOperation.Builder builder, long rawContactId, String itemType) { + return builder.withSelection( + ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?", + new String[]{ + Long.toString(rawContactId), itemType + }); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index a36af5c87..44c1e6b6c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -20,11 +20,7 @@ package org.sufficientlysecure.keychain.util; import android.content.Context; import android.content.SharedPreferences; -import android.os.Build; -import org.spongycastle.bcpg.CompressionAlgorithmTags; -import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; @@ -59,12 +55,8 @@ public class Preferences { } public void updateSharedPreferences(Context context) { - // multi-process preferences - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_MULTI_PROCESS); - } else { - mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_PRIVATE); - } + // multi-process safe preferences + mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_MULTI_PROCESS); } public String getLanguage() { @@ -103,60 +95,6 @@ public class Preferences { editor.commit(); } - public int getDefaultEncryptionAlgorithm() { - return mSharedPreferences.getInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM, - PGPEncryptedData.AES_256); - } - - public void setDefaultEncryptionAlgorithm(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM, value); - editor.commit(); - } - - public int getDefaultHashAlgorithm() { - return mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, - HashAlgorithmTags.SHA256); - } - - public void setDefaultHashAlgorithm(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, value); - editor.commit(); - } - - public int getDefaultMessageCompression() { - return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION, - CompressionAlgorithmTags.ZLIB); - } - - public void setDefaultMessageCompression(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION, value); - editor.commit(); - } - - public int getDefaultFileCompression() { - return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, - CompressionAlgorithmTags.UNCOMPRESSED); - } - - public void setDefaultFileCompression(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, value); - editor.commit(); - } - - public boolean getDefaultAsciiArmor() { - return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, false); - } - - public void setDefaultAsciiArmor(boolean value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, value); - editor.commit(); - } - public boolean getCachedConsolidate() { return mSharedPreferences.getBoolean(Pref.CACHED_CONSOLIDATE, false); } @@ -210,6 +148,7 @@ public class Preferences { } return servers.toArray(chunks); } + public String getPreferredKeyserver() { return getKeyServers()[0]; } @@ -231,21 +170,12 @@ public class Preferences { editor.commit(); } - public void setWriteVersionHeader(boolean conceal) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.Pref.WRITE_VERSION_HEADER, conceal); - editor.commit(); - } - - public boolean getWriteVersionHeader() { - return mSharedPreferences.getBoolean(Constants.Pref.WRITE_VERSION_HEADER, false); - } - public void setSearchKeyserver(boolean searchKeyserver) { SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putBoolean(Pref.SEARCH_KEYSERVER, searchKeyserver); editor.commit(); } + public void setSearchKeybase(boolean searchKeybase) { SharedPreferences.Editor editor = mSharedPreferences.edit(); editor.putBoolean(Pref.SEARCH_KEYBASE, searchKeybase); @@ -253,7 +183,7 @@ public class Preferences { } public CloudSearchPrefs getCloudSearchPrefs() { - return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true), + return new CloudSearchPrefs(mSharedPreferences.getBoolean(Pref.SEARCH_KEYSERVER, true), mSharedPreferences.getBoolean(Pref.SEARCH_KEYBASE, true), getPreferredKeyserver()); } @@ -301,26 +231,9 @@ public class Preferences { } setKeyServers(servers.toArray(new String[servers.size()])); - - // migrate old uncompressed constant to new one - if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, 0) - == 0x21070001) { - setDefaultFileCompression(CompressionAlgorithmTags.UNCOMPRESSED); - } - - // migrate away from MD5 - if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, 0) - == HashAlgorithmTags.MD5) { - setDefaultHashAlgorithm(HashAlgorithmTags.SHA256); - } } // fall through case 4: { - // for compatibility: change from SHA512 to SHA256 - if (mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, 0) - == HashAlgorithmTags.SHA512) { - setDefaultHashAlgorithm(HashAlgorithmTags.SHA256); - } } } |