diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 50aa4c480..21ccf0b9c 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -859,6 +859,11 @@ android:exported="false" android:theme="@style/Theme.Keychain.Transparent" android:label="@string/app_name" /> + mSecurityProblems; + DecryptVerifySecurityProblem mSecurityProblem; CryptoInputParcel mCachedCryptoInputParcel; @@ -73,7 +69,7 @@ public class DecryptVerifyResult extends InputPendingResult { mCachedCryptoInputParcel = source.readParcelable(CryptoInputParcel.class.getClassLoader()); mSkippedDisallowedKeys = source.createLongArray(); - mSecurityProblems = (ArrayList) source.readSerializable(); + mSecurityProblem = (DecryptVerifySecurityProblem) source.readSerializable(); } @@ -137,7 +133,7 @@ public class DecryptVerifyResult extends InputPendingResult { dest.writeParcelable(mCachedCryptoInputParcel, flags); dest.writeLongArray(mSkippedDisallowedKeys); - dest.writeSerializable(mSecurityProblems); + dest.writeSerializable(mSecurityProblem); } public static final Creator CREATOR = new Creator() { @@ -150,28 +146,11 @@ public class DecryptVerifyResult extends InputPendingResult { } }; - public void addSecurityProblem(SecurityProblem securityProblem) { - if (securityProblem == null) { - return; - } - if (mSecurityProblems == null) { - mSecurityProblems = new ArrayList<>(); - } - mSecurityProblems.add(securityProblem); + public DecryptVerifySecurityProblem getSecurityProblem() { + return mSecurityProblem; } - public void addSecurityProblems(List securityProblems) { - if (securityProblems == null) { - return; - } - if (mSecurityProblems == null) { - mSecurityProblems = new ArrayList<>(); - } - mSecurityProblems.addAll(securityProblems); - } - - public List getSecurityProblems() { - return mSecurityProblems != null ? - Collections.unmodifiableList(mSecurityProblems) : Collections.emptyList(); + public void setSecurityProblemResult(DecryptVerifySecurityProblem securityProblem) { + mSecurityProblem = securityProblem; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/DecryptVerifySecurityProblem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/DecryptVerifySecurityProblem.java new file mode 100644 index 000000000..c6178cd18 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/DecryptVerifySecurityProblem.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.pgp; + + +import java.io.Serializable; + +import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureSigningAlgorithm; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmProblem; + + +public class DecryptVerifySecurityProblem implements Serializable { + public final KeySecurityProblem encryptionKeySecurityProblem; + public final KeySecurityProblem signingKeySecurityProblem; + public final EncryptionAlgorithmProblem symmetricSecurityProblem; + public final InsecureSigningAlgorithm signatureSecurityProblem; + + private DecryptVerifySecurityProblem(DecryptVerifySecurityProblemBuilder builder) { + encryptionKeySecurityProblem = builder.encryptionKeySecurityProblem; + signingKeySecurityProblem = builder.signingKeySecurityProblem; + symmetricSecurityProblem = builder.symmetricSecurityProblem; + signatureSecurityProblem = builder.signatureSecurityProblem; + } + + public SecurityProblem getPrioritySecurityProblem() { + if (encryptionKeySecurityProblem != null) { + return encryptionKeySecurityProblem; + } else if (signingKeySecurityProblem != null) { + return signingKeySecurityProblem; + } else if (symmetricSecurityProblem != null) { + return symmetricSecurityProblem; + } else if (signatureSecurityProblem != null) { + return signatureSecurityProblem; + } else { + throw new IllegalStateException("No security problem?"); + } + } + + static class DecryptVerifySecurityProblemBuilder { + private KeySecurityProblem encryptionKeySecurityProblem; + private KeySecurityProblem signingKeySecurityProblem; + private EncryptionAlgorithmProblem symmetricSecurityProblem; + private InsecureSigningAlgorithm signatureSecurityProblem; + + void addEncryptionKeySecurityProblem(KeySecurityProblem encryptionKeySecurityProblem) { + this.encryptionKeySecurityProblem = encryptionKeySecurityProblem; + } + + void addSigningKeyProblem(KeySecurityProblem keySecurityProblem) { + this.signingKeySecurityProblem = keySecurityProblem; + } + + void addSymmetricSecurityProblem(EncryptionAlgorithmProblem symmetricSecurityProblem) { + this.symmetricSecurityProblem = symmetricSecurityProblem; + } + + void addSignatureSecurityProblem(InsecureSigningAlgorithm signatureSecurityProblem) { + this.signatureSecurityProblem = signatureSecurityProblem; + } + + public DecryptVerifySecurityProblem build() { + if (encryptionKeySecurityProblem == null && signingKeySecurityProblem == null && + symmetricSecurityProblem == null && signatureSecurityProblem == null) { + return null; + } + return new DecryptVerifySecurityProblem(this); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java index e2c0b6b63..199f7c445 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java @@ -17,33 +17,20 @@ package org.sufficientlysecure.keychain.pgp; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.openintents.openpgp.OpenPgpDecryptionResult; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.SecurityProblem.SymmetricAlgorithmProblem; import org.sufficientlysecure.keychain.util.Log; -public class OpenPgpDecryptionResultBuilder { +class OpenPgpDecryptionResultBuilder { // builder + private boolean isInsecure = false; private boolean isEncrypted = false; private byte[] sessionKey; private byte[] decryptedSessionKey; - private ArrayList securityProblems; - public void addSecurityProblem(SecurityProblem securityProblem) { - if (securityProblems == null) { - securityProblems = new ArrayList<>(); - } - securityProblems.add(securityProblem); - } - - public List getKeySecurityProblems() { - return securityProblems != null ? Collections.unmodifiableList(securityProblems) : null; + public void setInsecure(boolean insecure) { + this.isInsecure = insecure; } public void setEncrypted(boolean encrypted) { @@ -51,15 +38,14 @@ public class OpenPgpDecryptionResultBuilder { } public OpenPgpDecryptionResult build() { - if (securityProblems != null && !securityProblems.isEmpty()) { + if (isInsecure) { Log.d(Constants.TAG, "RESULT_INSECURE"); return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_INSECURE, sessionKey, decryptedSessionKey); } if (isEncrypted) { Log.d(Constants.TAG, "RESULT_ENCRYPTED"); - return new OpenPgpDecryptionResult( - OpenPgpDecryptionResult.RESULT_ENCRYPTED, sessionKey, decryptedSessionKey); + return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED, sessionKey, decryptedSessionKey); } Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED"); @@ -67,12 +53,11 @@ public class OpenPgpDecryptionResultBuilder { } - public void setSessionKey(byte[] sessionKey, byte[] decryptedSessionKey) { + void setSessionKey(byte[] sessionKey, byte[] decryptedSessionKey) { if ((sessionKey == null) != (decryptedSessionKey == null)) { throw new AssertionError("sessionKey must be null iff decryptedSessionKey is null!"); } this.sessionKey = sessionKey; this.decryptedSessionKey = decryptedSessionKey; } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 83b99776f..9c04c5394 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -19,9 +19,7 @@ package org.sufficientlysecure.keychain.pgp; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; -import java.util.List; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult; @@ -55,9 +53,9 @@ public class OpenPgpSignatureResultBuilder { private boolean mIsSignatureKeyCertified = false; private boolean mIsKeyRevoked = false; private boolean mIsKeyExpired = false; + private boolean mInsecure = false; private String mSenderAddress; private Date mSignatureTimestamp; - private ArrayList mSecurityProblems; public OpenPgpSignatureResultBuilder(KeyRepository keyRepository) { this.mKeyRepository = keyRepository; @@ -83,15 +81,8 @@ public class OpenPgpSignatureResultBuilder { this.mValidSignature = validSignature; } - public void addSecurityProblem(SecurityProblem securityProblem) { - if (mSecurityProblems == null) { - mSecurityProblems = new ArrayList<>(); - } - mSecurityProblems.add(securityProblem); - } - - public List getSecurityProblems() { - return mSecurityProblems != null ? Collections.unmodifiableList(mSecurityProblems) : null; + public void setInsecure(boolean insecure) { + this.mInsecure = insecure; } public void setSignatureKeyCertified(boolean isSignatureKeyCertified) { @@ -115,6 +106,10 @@ public class OpenPgpSignatureResultBuilder { this.mConfirmedUserIds = confirmedUserIds; } + public boolean isInsecure() { + return mInsecure; + } + public void initValid(CanonicalizedPublicKey signingKey) { setSignatureAvailable(true); setKnownKey(true); @@ -189,7 +184,7 @@ public class OpenPgpSignatureResultBuilder { } else if (mIsKeyExpired) { Log.d(Constants.TAG, "RESULT_INVALID_KEY_EXPIRED"); signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED; - } else if (mSecurityProblems != null && !mSecurityProblems.isEmpty()) { + } else if (mInsecure) { Log.d(Constants.TAG, "RESULT_INVALID_INSECURE"); signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE; } else if (mIsSignatureKeyCertified) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index d728b185c..1ec57e727 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -68,9 +68,11 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem.DecryptVerifySecurityProblemBuilder; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength; import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; -import org.sufficientlysecure.keychain.pgp.SecurityProblem.SymmetricAlgorithmProblem; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmProblem; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; @@ -301,6 +303,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation getSecurityProblems() { - return signatureResultBuilder.getSecurityProblems(); - } - /** * Mostly taken from ClearSignedFileProcessor in Bouncy Castle */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SecurityProblem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SecurityProblem.java index 58819636a..4a703b6c6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SecurityProblem.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SecurityProblem.java @@ -18,10 +18,40 @@ package org.sufficientlysecure.keychain.pgp; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.bouncycastle.util.encoders.Base64; + public abstract class SecurityProblem implements Serializable { + public String getIdentifier() { + if (!isIdentifiable()) { + return null; + } + + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(out); + oos.writeObject(this); + oos.close(); + + byte[] digest = MessageDigest.getInstance("SHA1").digest(out.toByteArray()); + return Base64.toBase64String(digest); + } catch (NoSuchAlgorithmException | IOException e) { + throw new IllegalStateException(e); + } + } + + public boolean isIdentifiable() { + return false; + } + public static abstract class KeySecurityProblem extends SecurityProblem { public final long masterKeyId; public final long subKeyId; @@ -32,10 +62,25 @@ public abstract class SecurityProblem implements Serializable { this.subKeyId = subKeyId; this.algorithm = algorithm; } + + @Override + public boolean isIdentifiable() { + return true; + } } - public static abstract class SymmetricAlgorithmProblem extends SecurityProblem { + public static abstract class EncryptionAlgorithmProblem extends SecurityProblem { + @SuppressWarnings("unused") // used for identifying this specific problem + private final byte[] sessionKey; + private EncryptionAlgorithmProblem(byte[] sessionKey) { + this.sessionKey = sessionKey; + } + + @Override + public boolean isIdentifiable() { + return sessionKey != null; + } } public static class InsecureBitStrength extends KeySecurityProblem { @@ -62,23 +107,26 @@ public abstract class SecurityProblem implements Serializable { } } - public static class InsecureHashAlgorithm extends SecurityProblem { + public static class InsecureSigningAlgorithm extends SecurityProblem { public final int hashAlgorithm; - InsecureHashAlgorithm(int hashAlgorithm) { + InsecureSigningAlgorithm(int hashAlgorithm) { this.hashAlgorithm = hashAlgorithm; } } - public static class InsecureSymmetricAlgorithm extends SymmetricAlgorithmProblem { + public static class InsecureEncryptionAlgorithm extends EncryptionAlgorithmProblem { public final int symmetricAlgorithm; - InsecureSymmetricAlgorithm(int symmetricAlgorithm) { + InsecureEncryptionAlgorithm(byte[] sessionKey, int symmetricAlgorithm) { + super(sessionKey); this.symmetricAlgorithm = symmetricAlgorithm; } } - public static class MissingMdc extends SymmetricAlgorithmProblem { - + public static class MissingMdc extends EncryptionAlgorithmProblem { + MissingMdc(byte[] sessionKey) { + super(sessionKey); + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index a45e943e2..a75d1e04a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -90,6 +90,10 @@ public class KeychainContract { String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name } + interface OverriddenWarnings { + String IDENTIFIER = "identifier"; + } + public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY; private static final Uri BASE_CONTENT_URI_INTERNAL = Uri diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index ae822515e..58266db08 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; +import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings; import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity; @@ -51,7 +52,7 @@ import org.sufficientlysecure.keychain.util.Log; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 20; + private static final int DATABASE_VERSION = 21; private Context mContext; public interface Tables { @@ -63,6 +64,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { String CERTS = "certs"; String API_APPS = "api_apps"; String API_ALLOWED_KEYS = "api_allowed_keys"; + String OVERRIDDEN_WARNINGS = "overridden_warnings"; } private static final String CREATE_KEYRINGS_PUBLIC = @@ -172,6 +174,12 @@ public class KeychainDatabase extends SQLiteOpenHelper { + Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + ")"; + private static final String CREATE_OVERRIDDEN_WARNINGS = + "CREATE TABLE IF NOT EXISTS " + Tables.OVERRIDDEN_WARNINGS + " (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + OverriddenWarnings.IDENTIFIER + " TEXT NOT NULL UNIQUE " + + ")"; + public KeychainDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); mContext = context; @@ -281,10 +289,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { case 15: db.execSQL("CREATE INDEX uids_by_name ON user_packets (name COLLATE NOCASE)"); db.execSQL("CREATE INDEX uids_by_email ON user_packets (email COLLATE NOCASE)"); - if (oldVersion == 14) { - // no consolidate necessary - return; - } case 16: // splitUserId changed: Execute consolidate for new parsing of name, email case 17: @@ -294,10 +298,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { case 19: // emergency fix for crashing consolidate db.execSQL("UPDATE keys SET is_secure = 1;"); - if (oldVersion == 18 || oldVersion == 19) { - // no consolidate for now, often crashes! - return; - } /* TODO actually drop this table. leaving it around for now! case 20: db.execSQL("DROP TABLE api_accounts"); @@ -306,6 +306,12 @@ public class KeychainDatabase extends SQLiteOpenHelper { return; } */ + case 20: + db.execSQL(CREATE_OVERRIDDEN_WARNINGS); + if (oldVersion == 18 || oldVersion == 19 || oldVersion == 20) { + // no consolidate for now, often crashes! + return; + } } // TODO: don't depend on consolidate! make migrations inline! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java new file mode 100644 index 000000000..81be61350 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.provider; + + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; + + +public class OverriddenWarningsRepository { + private final Context context; + private KeychainDatabase keychainDatabase; + + public static OverriddenWarningsRepository createOverriddenWarningsRepository(Context context) { + return new OverriddenWarningsRepository(context); + } + + private OverriddenWarningsRepository(Context context) { + this.context = context; + } + + private KeychainDatabase getDb() { + if (keychainDatabase == null) { + keychainDatabase = new KeychainDatabase(context); + } + return keychainDatabase; + } + + public boolean isWarningOverridden(String identifier) { + SQLiteDatabase db = getDb().getReadableDatabase(); + Cursor cursor = db.query( + Tables.OVERRIDDEN_WARNINGS, + new String[] { "COUNT(*)" }, + OverriddenWarnings.IDENTIFIER + " = ?", + new String[] { identifier }, + null, null, null); + + try { + cursor.moveToFirst(); + return cursor.getInt(0) > 0; + } finally { + cursor.close(); + db.close(); + } + } + + public void putOverride(String identifier) { + SQLiteDatabase db = getDb().getWritableDatabase(); + ContentValues cv = new ContentValues(); + cv.put(OverriddenWarnings.IDENTIFIER, identifier); + db.replace(Tables.OVERRIDDEN_WARNINGS, null, cv); + db.close(); + } + + public void deleteOverride(String identifier) { + SQLiteDatabase db = getDb().getWritableDatabase(); + db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier }); + db.close(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index ea2298b23..ea26384c3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -25,12 +25,14 @@ import android.content.Context; import android.content.Intent; import android.os.Build; +import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.remote.ui.RemoteBackupActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteErrorActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteImportKeysActivity; import org.sufficientlysecure.keychain.remote.ui.RemotePassphraseDialogActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteRegisterActivity; +import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityProblemDialogActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteSelectPubKeyActivity; import org.sufficientlysecure.keychain.remote.ui.RequestKeyPermissionActivity; @@ -141,6 +143,22 @@ public class ApiPendingIntentFactory { return createInternal(data, intent); } + PendingIntent createSecurityProblemIntent(String packageName, DecryptVerifySecurityProblem securityProblem, + boolean supportOverride) { + Intent intent = new Intent(mContext, RemoteSecurityProblemDialogActivity.class); + intent.putExtra(RemoteSecurityProblemDialogActivity.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(RemoteSecurityProblemDialogActivity.EXTRA_SECURITY_PROBLEM, securityProblem); + intent.putExtra(RemoteSecurityProblemDialogActivity.EXTRA_SUPPORT_OVERRIDE, supportOverride); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + //noinspection ResourceType, looks like lint is missing FLAG_IMMUTABLE + return PendingIntent.getActivity(mContext, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + return PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + } + } + private PendingIntent createInternal(Intent data, Intent intent) { // re-attach "data" for pass through. It will be used later to repeat pgp operation intent.putExtra(RemoteSecurityTokenOperationActivity.EXTRA_DATA, data); @@ -175,5 +193,4 @@ public class ApiPendingIntentFactory { PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); } } - } 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 4ee644664..1f788a6ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -53,6 +53,7 @@ import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; @@ -60,11 +61,13 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.SecurityProblem; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; @@ -400,6 +403,7 @@ public class OpenPgpService extends Service { processDecryptionResultForResultIntent(targetApiVersion, result, pgpResult.getDecryptionResult()); processMetadataForResultIntent(result, pgpResult.getDecryptionMetadata()); processSignatureResultForResultIntent(targetApiVersion, data, result, pgpResult); + processSecurityProblemsPendingIntent(data, result, pgpResult); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); return result; @@ -427,6 +431,29 @@ public class OpenPgpService extends Service { } } + private void processSecurityProblemsPendingIntent(Intent data, Intent result, + DecryptVerifyResult decryptVerifyResult) { + DecryptVerifySecurityProblem securityProblem = decryptVerifyResult.getSecurityProblem(); + if (securityProblem == null) { + return; + } + + boolean supportOverride = data.getBooleanExtra(OpenPgpApi.EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING, false); + if (supportOverride) { + SecurityProblem prioritySecurityProblem = securityProblem.getPrioritySecurityProblem(); + if (prioritySecurityProblem.isIdentifiable()) { + String identifier = prioritySecurityProblem.getIdentifier(); + boolean isOverridden = OverriddenWarningsRepository.createOverriddenWarningsRepository(this) + .isWarningOverridden(identifier); + result.putExtra(OpenPgpApi.RESULT_OVERRIDE_CRYPTO_WARNING, isOverridden); + } + } + + String packageName = mApiPermissionHelper.getCurrentCallingPackage(); + result.putExtra(OpenPgpApi.RESULT_INSECURE_DETAIL_INTENT, + mApiPendingIntentFactory.createSecurityProblemIntent(packageName, securityProblem, supportOverride)); + } + private void processDecryptionResultForResultIntent(int targetApiVersion, Intent result, OpenPgpDecryptionResult decryptionResult) { if (targetApiVersion < API_VERSION_WITH_DECRYPTION_RESULT) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSecurityProblemDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSecurityProblemDialogActivity.java new file mode 100644 index 000000000..254597b5c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSecurityProblemDialogActivity.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + + +import java.io.Serializable; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.remote.ui.SecurityProblemPresenter.RemoteSecurityProblemView; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator; + + +public class RemoteSecurityProblemDialogActivity extends FragmentActivity { + public static final String EXTRA_PACKAGE_NAME = "package_name"; + public static final String EXTRA_SECURITY_PROBLEM = "security_problem"; + public static final String EXTRA_SUPPORT_OVERRIDE = "support_override"; + + + private SecurityProblemPresenter presenter; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + this.presenter = new SecurityProblemPresenter(getBaseContext()); + + if (savedInstanceState == null) { + RemoteRegisterDialogFragment frag = new RemoteRegisterDialogFragment(); + frag.show(getSupportFragmentManager(), "requestKeyDialog"); + } + } + + @Override + protected void onStart() { + super.onStart(); + + Intent intent = getIntent(); + String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + Serializable keySecurityProblem = intent.getSerializableExtra(EXTRA_SECURITY_PROBLEM); + boolean supportOverride = intent.getBooleanExtra(EXTRA_SUPPORT_OVERRIDE, false); + + presenter.setupFromIntentData(packageName, keySecurityProblem, supportOverride); + } + + public static class RemoteRegisterDialogFragment extends DialogFragment { + public static final int SECONDARY_CHILD_NONE = 0; + public static final int SECONDARY_CHILD_RECOMMENDATION = 1; + public static final int SECONDARY_CHILD_OVERRIDE = 2; + public static final int BUTTON_BAR_REGULAR = 0; + public static final int BUTTON_BAR_OVERRIDE = 1; + + private SecurityProblemPresenter presenter; + private RemoteSecurityProblemView mvpView; + + private Button buttonGotIt; + private Button buttonViewKey; + private Button buttonOverride; + private Button buttonOverrideUndo; + private Button buttonOverrideBack; + private Button buttonOverrideConfirm; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); + + @SuppressLint("InflateParams") + View view = LayoutInflater.from(theme).inflate(R.layout.remote_security_issue_dialog, null, false); + alert.setView(view); + + buttonGotIt = (Button) view.findViewById(R.id.button_allow); + buttonViewKey = (Button) view.findViewById(R.id.button_view_key); + buttonOverride = (Button) view.findViewById(R.id.button_override); + buttonOverrideUndo = (Button) view.findViewById(R.id.button_override_undo); + buttonOverrideBack = (Button) view.findViewById(R.id.button_override_back); + buttonOverrideConfirm = (Button) view.findViewById(R.id.button_override_confirm); + + setupListenersForPresenter(); + mvpView = createMvpView(view); + + return alert.create(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + presenter = ((RemoteSecurityProblemDialogActivity) getActivity()).presenter; + presenter.setView(mvpView); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + + if (presenter != null) { + presenter.onCancel(); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + + if (presenter != null) { + presenter.setView(null); + presenter = null; + } + } + + @NonNull + private RemoteSecurityProblemView createMvpView(View view) { + final LinearLayout insecureWarningLayout = (LinearLayout) view.findViewById(R.id.insecure_warning_layout); + final ImageView iconClientApp = (ImageView) view.findViewById(R.id.icon_client_app); + final TextView explanationText = (TextView) insecureWarningLayout.findViewById(R.id.dialog_insecure_text); + final TextView recommendText = + (TextView) insecureWarningLayout.findViewById(R.id.dialog_insecure_recommend_text); + final TextView overrideText = + (TextView) insecureWarningLayout.findViewById(R.id.dialog_insecure_override_text); + final ToolableViewAnimator secondaryLayoutAnimator = + (ToolableViewAnimator) insecureWarningLayout.findViewById(R.id.dialog_insecure_secondary_layout); + final ToolableViewAnimator buttonBarAnimator = + (ToolableViewAnimator) view.findViewById(R.id.dialog_insecure_button_bar); + + return new RemoteSecurityProblemView() { + private boolean layoutInitialized = false; + + @Override + public void finishAsCancelled() { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + activity.setResult(RESULT_CANCELED); + activity.finish(); + } + + @Override + public void finishAsSuppressed() { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + activity.setResult(RESULT_OK); + activity.finish(); + } + + @Override + public void setTitleClientIcon(Drawable drawable) { + iconClientApp.setImageDrawable(drawable); + } + + /* specialized layouts, for later? + private void inflateWarningContentLayout(int dialog_insecure_mdc) { + insecureWarningLayout.removeAllViews(); + getLayoutInflater(null).inflate(dialog_insecure_mdc, insecureWarningLayout); + } + */ + + private void showGeneric(String explanationString) { + explanationText.setText(explanationString); + secondaryLayoutAnimator.setDisplayedChild(SECONDARY_CHILD_NONE, layoutInitialized); + buttonBarAnimator.setDisplayedChild(BUTTON_BAR_REGULAR, layoutInitialized); + layoutInitialized = true; + } + + private void showGenericWithRecommendation( + @StringRes int explanationStringRes, @StringRes int recommendationStringRes) { + explanationText.setText(explanationStringRes); + recommendText.setText(recommendationStringRes); + secondaryLayoutAnimator.setDisplayedChild(SECONDARY_CHILD_RECOMMENDATION, layoutInitialized); + buttonBarAnimator.setDisplayedChild(BUTTON_BAR_REGULAR, layoutInitialized); + layoutInitialized = true; + } + + private void showGenericWithRecommendation( + String explanationString, @StringRes int recommendationStringRes) { + explanationText.setText(explanationString); + recommendText.setText(recommendationStringRes); + secondaryLayoutAnimator.setDisplayedChild(SECONDARY_CHILD_RECOMMENDATION, layoutInitialized); + buttonBarAnimator.setDisplayedChild(BUTTON_BAR_REGULAR, layoutInitialized); + layoutInitialized = true; + } + + @Override + public void showLayoutMissingMdc() { + showGenericWithRecommendation(R.string.insecure_mdc, R.string.insecure_mdc_suggestion); + } + + @Override + public void showLayoutInsecureSymmetric(int symmetricAlgorithm) { + showGeneric(getString(R.string.insecure_symmetric_algo, + KeyFormattingUtils.getSymmetricCipherName(symmetricAlgorithm))); + } + + @Override + public void showLayoutInsecureHashAlgorithm(int hashAlgorithm) { + showGeneric(getString(R.string.insecure_hash_algo, + KeyFormattingUtils.getHashAlgoName(hashAlgorithm))); + } + + @Override + public void showLayoutEncryptInsecureBitsize(int algorithmId, int bitStrength) { + String algorithmName = KeyFormattingUtils.getAlgorithmInfo(algorithmId, null, null); + + showGenericWithRecommendation( + getString(R.string.insecure_encrypt_bitstrength, algorithmName), + R.string.insecure_encrypt_bitstrength_suggestion); + } + + @Override + public void showLayoutSignInsecureBitsize(int algorithmId, int bitStrength) { + String algorithmName = KeyFormattingUtils.getAlgorithmInfo(algorithmId, null, null); + + showGenericWithRecommendation( + getString(R.string.insecure_sign_bitstrength, algorithmName), + R.string.insecure_sign_bitstrength_suggestion); + } + + @Override + public void showLayoutEncryptNotWhitelistedCurve(String curveOid) { + showGenericWithRecommendation( + getString(R.string.insecure_encrypt_not_whitelisted_curve, curveOid), + R.string.insecure_report_suggestion + ); + } + + @Override + public void showLayoutSignNotWhitelistedCurve(String curveOid) { + showGenericWithRecommendation( + getString(R.string.insecure_sign_not_whitelisted_curve, curveOid), + R.string.insecure_report_suggestion + ); + } + + @Override + public void showLayoutEncryptUnidentifiedKeyProblem() { + showGenericWithRecommendation( + R.string.insecure_encrypt_unidentified, + R.string.insecure_report_suggestion + ); + } + + @Override + public void showLayoutSignUnidentifiedKeyProblem() { + showGenericWithRecommendation( + R.string.insecure_sign_unidentified, + R.string.insecure_report_suggestion + ); + } + + @Override + public void showOverrideMessage(int countdown) { + secondaryLayoutAnimator.setDisplayedChild(SECONDARY_CHILD_OVERRIDE, true); + buttonBarAnimator.setDisplayedChild(BUTTON_BAR_OVERRIDE, true); + overrideText.setText(getString(R.string.dialog_insecure_override, countdown)); + buttonOverrideConfirm.setText( + getString(R.string.dialog_insecure_button_override_confirm, countdown)); + } + + @Override + public void showViewKeyButton() { + buttonViewKey.setVisibility(View.VISIBLE); + } + + @Override + public void showOverrideButton() { + buttonOverride.setVisibility(View.VISIBLE); + buttonOverrideUndo.setVisibility(View.GONE); + } + + @Override + public void showOverrideUndoButton() { + buttonOverride.setVisibility(View.GONE); + buttonOverrideUndo.setVisibility(View.VISIBLE); + } + }; + } + + private void setupListenersForPresenter() { + buttonGotIt.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickGotIt(); + } + }); + buttonViewKey.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickViewKey(); + } + }); + buttonOverride.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickOverride(); + } + }); + buttonOverrideUndo.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickOverrideUndo(); + } + }); + buttonOverrideBack.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickOverrideBack(); + } + }); + buttonOverrideConfirm.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + presenter.onClickOverrideConfirm(); + } + }); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java new file mode 100644 index 000000000..cdb33243e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + + +import java.io.Serializable; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; + +import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureSigningAlgorithm; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureEncryptionAlgorithm; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.NotWhitelistedCurve; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmProblem; +import org.sufficientlysecure.keychain.pgp.SecurityProblem.UnidentifiedKeyProblem; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; +import org.sufficientlysecure.keychain.ui.ViewKeyActivity; + + +class SecurityProblemPresenter { + private static final int OVERRIDE_REQUIRED_COUNT = 3; + + + private final Context context; + private final PackageManager packageManager; + private final OverriddenWarningsRepository overriddenWarningsRepository; + + + private RemoteSecurityProblemView view; + private Long viewKeyMasterKeyId; + private int overrideCounter; + private String securityProblemIdentifier; + + private String packageName; + private Serializable securityProblem; + private boolean supportOverride; + + + SecurityProblemPresenter(Context context) { + this.context = context; + packageManager = context.getPackageManager(); + overriddenWarningsRepository = OverriddenWarningsRepository.createOverriddenWarningsRepository(context); + } + + public void setView(RemoteSecurityProblemView view) { + this.view = view; + } + + void setupFromIntentData(String packageName, Serializable securityProblem, boolean supportOverride) { + this.packageName = packageName; + this.securityProblem = securityProblem; + this.supportOverride = supportOverride; + + refreshSecurityProblemDisplay(); + refreshPackageInfo(); + } + + private void refreshSecurityProblemDisplay() { + if (securityProblem instanceof DecryptVerifySecurityProblem) { + setupFromDecryptVerifySecurityProblem((DecryptVerifySecurityProblem) securityProblem); + } else { + throw new IllegalArgumentException("Unhandled security problem type!"); + } + } + + private void setupFromDecryptVerifySecurityProblem(DecryptVerifySecurityProblem securityProblem) { + if (securityProblem.encryptionKeySecurityProblem != null) { + setupFromEncryptionKeySecurityProblem(securityProblem.encryptionKeySecurityProblem); + } else if (securityProblem.signingKeySecurityProblem != null) { + setupFromSigningKeySecurityProblem(securityProblem.signingKeySecurityProblem); + } else if (securityProblem.symmetricSecurityProblem != null) { + setupFromEncryptionAlgorithmSecurityProblem(securityProblem.symmetricSecurityProblem); + } else if (securityProblem.signatureSecurityProblem != null) { + setupFromSignatureSecurityProblem(securityProblem.signatureSecurityProblem); + } + } + + private void setupFromEncryptionKeySecurityProblem(KeySecurityProblem keySecurityProblem) { + viewKeyMasterKeyId = keySecurityProblem.masterKeyId; + view.showViewKeyButton(); + + if (keySecurityProblem instanceof InsecureBitStrength) { + InsecureBitStrength problem = (InsecureBitStrength) keySecurityProblem; + view.showLayoutEncryptInsecureBitsize(problem.algorithm, problem.bitStrength); + } else if (keySecurityProblem instanceof NotWhitelistedCurve) { + NotWhitelistedCurve problem = (NotWhitelistedCurve) keySecurityProblem; + view.showLayoutEncryptNotWhitelistedCurve(problem.curveOid); + } else if (keySecurityProblem instanceof UnidentifiedKeyProblem) { + view.showLayoutEncryptUnidentifiedKeyProblem(); + } else { + throw new IllegalArgumentException("Unhandled key security problem type!"); + } + + if (keySecurityProblem.isIdentifiable()) { + securityProblemIdentifier = keySecurityProblem.getIdentifier(); + refreshOverrideStatusView(); + } + } + + private void setupFromSigningKeySecurityProblem(KeySecurityProblem keySecurityProblem) { + viewKeyMasterKeyId = keySecurityProblem.masterKeyId; + view.showViewKeyButton(); + + if (keySecurityProblem instanceof InsecureBitStrength) { + InsecureBitStrength problem = (InsecureBitStrength) keySecurityProblem; + view.showLayoutSignInsecureBitsize(problem.algorithm, problem.bitStrength); + } else if (keySecurityProblem instanceof NotWhitelistedCurve) { + NotWhitelistedCurve problem = (NotWhitelistedCurve) keySecurityProblem; + view.showLayoutSignNotWhitelistedCurve(problem.curveOid); + } else if (keySecurityProblem instanceof UnidentifiedKeyProblem) { + view.showLayoutSignUnidentifiedKeyProblem(); + } else { + throw new IllegalArgumentException("Unhandled key security problem type!"); + } + + if (keySecurityProblem.isIdentifiable()) { + securityProblemIdentifier = keySecurityProblem.getIdentifier(); + refreshOverrideStatusView(); + } + } + + private void setupFromEncryptionAlgorithmSecurityProblem(EncryptionAlgorithmProblem securityProblem) { + if (securityProblem instanceof MissingMdc) { + view.showLayoutMissingMdc(); + } else if (securityProblem instanceof InsecureEncryptionAlgorithm) { + InsecureEncryptionAlgorithm insecureSymmetricAlgorithm = (InsecureEncryptionAlgorithm) securityProblem; + view.showLayoutInsecureSymmetric(insecureSymmetricAlgorithm.symmetricAlgorithm); + } else { + throw new IllegalArgumentException("Unhandled symmetric algorithm problem type!"); + } + + if (securityProblem.isIdentifiable()) { + securityProblemIdentifier = securityProblem.getIdentifier(); + refreshOverrideStatusView(); + } + } + + private void refreshOverrideStatusView() { + if (supportOverride) { + if (overriddenWarningsRepository.isWarningOverridden(securityProblemIdentifier)) { + view.showOverrideUndoButton(); + } else { + view.showOverrideButton(); + } + } + } + + private void setupFromSignatureSecurityProblem(InsecureSigningAlgorithm signatureSecurityProblem) { + view.showLayoutInsecureHashAlgorithm(signatureSecurityProblem.hashAlgorithm); + } + + private void refreshPackageInfo() { + ApplicationInfo applicationInfo; + try { + applicationInfo = packageManager.getApplicationInfo(packageName, 0); + } catch (NameNotFoundException e) { + throw new IllegalStateException("Could not retrieve package info!"); + } + Drawable appIcon = packageManager.getApplicationIcon(applicationInfo); + // CharSequence appName = packageManager.getApplicationLabel(applicationInfo); + + view.setTitleClientIcon(appIcon); + } + + private void incrementOverrideAndDisplayOrTrigger() { + int overrideCountLeft = OVERRIDE_REQUIRED_COUNT - overrideCounter; + if (overrideCountLeft > 0) { + overrideCounter++; + view.showOverrideMessage(overrideCountLeft); + } else { + overriddenWarningsRepository.putOverride(securityProblemIdentifier); + view.finishAsSuppressed(); + } + } + + private void resetOverrideStatus() { + overrideCounter = 0; + overriddenWarningsRepository.deleteOverride(securityProblemIdentifier); + } + + void onClickGotIt() { + view.finishAsCancelled(); + } + + void onClickViewKey() { + Intent viewKeyIntent = new Intent(context, ViewKeyActivity.class); + viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(viewKeyMasterKeyId)); + context.startActivity(viewKeyIntent); + } + + void onClickOverride() { + incrementOverrideAndDisplayOrTrigger(); + } + + void onClickOverrideUndo() { + resetOverrideStatus(); + refreshSecurityProblemDisplay(); + } + + void onClickOverrideBack() { + resetOverrideStatus(); + refreshSecurityProblemDisplay(); + } + + void onClickOverrideConfirm() { + incrementOverrideAndDisplayOrTrigger(); + } + + void onCancel() { + view.finishAsCancelled(); + } + + interface RemoteSecurityProblemView { + void finishAsCancelled(); + void finishAsSuppressed(); + void setTitleClientIcon(Drawable drawable); + + void showLayoutEncryptInsecureBitsize(int algorithmId, int bitStrength); + void showLayoutEncryptNotWhitelistedCurve(String curveOid); + void showLayoutEncryptUnidentifiedKeyProblem(); + void showLayoutSignInsecureBitsize(int algorithmId, int bitStrength); + void showLayoutSignNotWhitelistedCurve(String curveOid); + void showLayoutSignUnidentifiedKeyProblem(); + + void showLayoutMissingMdc(); + void showLayoutInsecureSymmetric(int symmetricAlgorithm); + + void showLayoutInsecureHashAlgorithm(int hashAlgorithm); + + void showOverrideMessage(int countdown); + + void showViewKeyButton(); + void showOverrideButton(); + void showOverrideUndoButton(); + } +} 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 c6c4b6954..338330bc0 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 @@ -18,6 +18,15 @@ package org.sufficientlysecure.keychain.ui.util; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.Locale; + import android.content.Context; import android.content.res.Resources; import android.graphics.Color; @@ -34,8 +43,12 @@ import android.widget.ViewAnimator; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; +import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.jcajce.PGPUtil; import org.bouncycastle.util.encoders.Hex; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpSignatureResult; @@ -48,14 +61,6 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.util.Log; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.DigestException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Collection; -import java.util.Locale; - public class KeyFormattingUtils { public static String getAlgorithmInfo(int algorithm, Integer keySize, String oid) { @@ -226,6 +231,47 @@ public class KeyFormattingUtils { } } + public static String getHashAlgoName(int hashAlgo) { + try { + return PGPUtil.getDigestName(hashAlgo); + } catch (PGPException e) { + return "#" + hashAlgo; + } + } + + public static String getSymmetricCipherName(int algorithm) { + switch (algorithm) { + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + return "Triple-DES"; + case SymmetricKeyAlgorithmTags.IDEA: + return "IDEA"; + case SymmetricKeyAlgorithmTags.CAST5: + return "CAST5"; + case SymmetricKeyAlgorithmTags.BLOWFISH: + return "Blowfish"; + case SymmetricKeyAlgorithmTags.SAFER: + return "SAFER"; + case SymmetricKeyAlgorithmTags.DES: + return "DES"; + case SymmetricKeyAlgorithmTags.AES_128: + return "AES-128"; + case SymmetricKeyAlgorithmTags.AES_192: + return "AES-192"; + case SymmetricKeyAlgorithmTags.AES_256: + return "AES-256"; + case SymmetricKeyAlgorithmTags.CAMELLIA_128: + return "Camellia-128"; + case SymmetricKeyAlgorithmTags.CAMELLIA_192: + return "Camellia-192"; + case SymmetricKeyAlgorithmTags.CAMELLIA_256: + return "Camellia-256"; + case SymmetricKeyAlgorithmTags.TWOFISH: + return "Twofish"; + default: + return "#" + algorithm; + } + } + /** * Converts fingerprint to hex *

diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java index 34e7b639a..961c40844 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/FoldableLinearLayout.java @@ -56,6 +56,7 @@ public class FoldableLinearLayout extends LinearLayout { private String mFoldedLabel; private String mUnFoldedLabel; + private boolean mInitializeFolded; public FoldableLinearLayout(Context context) { super(context); @@ -84,6 +85,7 @@ public class FoldableLinearLayout extends LinearLayout { R.styleable.FoldableLinearLayout, 0, 0); mFoldedLabel = a.getString(R.styleable.FoldableLinearLayout_foldedLabel); mUnFoldedLabel = a.getString(R.styleable.FoldableLinearLayout_unFoldedLabel); + mInitializeFolded = a.getBoolean(R.styleable.FoldableLinearLayout_initializeFolded, true); a.recycle(); } // If any attribute isn't found then set a default one @@ -102,6 +104,10 @@ public class FoldableLinearLayout extends LinearLayout { initialiseInnerViews(); + if (!mInitializeFolded) { + toggleFolded(); + } + super.onFinishInflate(); } @@ -149,42 +155,46 @@ public class FoldableLinearLayout extends LinearLayout { foldableControl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - mFolded = !mFolded; - if (mFolded) { - mFoldableIcon.setImageResource(R.drawable.ic_expand_less_black_24dp); - mFoldableContainer.setVisibility(View.VISIBLE); - AlphaAnimation animation = new AlphaAnimation(0f, 1f); - animation.setDuration(mShortAnimationDuration); - mFoldableContainer.startAnimation(animation); - mFoldableTextView.setText(mUnFoldedLabel); - - } else { - mFoldableIcon.setImageResource(R.drawable.ic_expand_more_black_24dp); - AlphaAnimation animation = new AlphaAnimation(1f, 0f); - animation.setDuration(mShortAnimationDuration); - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - // making sure that at the end the container is completely removed from view - mFoldableContainer.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mFoldableContainer.startAnimation(animation); - mFoldableTextView.setText(mFoldedLabel); - } + toggleFolded(); } }); } + private void toggleFolded() { + mFolded = !mFolded; + if (mFolded) { + mFoldableIcon.setImageResource(R.drawable.ic_expand_less_black_24dp); + mFoldableContainer.setVisibility(View.VISIBLE); + AlphaAnimation animation = new AlphaAnimation(0f, 1f); + animation.setDuration(mShortAnimationDuration); + mFoldableContainer.startAnimation(animation); + mFoldableTextView.setText(mUnFoldedLabel); + + } else { + mFoldableIcon.setImageResource(R.drawable.ic_expand_more_black_24dp); + AlphaAnimation animation = new AlphaAnimation(1f, 0f); + animation.setDuration(mShortAnimationDuration); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + // making sure that at the end the container is completely removed from view + mFoldableContainer.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + mFoldableContainer.startAnimation(animation); + mFoldableTextView.setText(mFoldedLabel); + } + } + /** * Adds provided child view to foldableContainer View * diff --git a/OpenKeychain/src/main/res/layout/dialog_insecure_bitsize.xml b/OpenKeychain/src/main/res/layout/dialog_insecure_bitsize.xml new file mode 100644 index 000000000..e913e04ab --- /dev/null +++ b/OpenKeychain/src/main/res/layout/dialog_insecure_bitsize.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/layout/dialog_insecure_generic.xml b/OpenKeychain/src/main/res/layout/dialog_insecure_generic.xml new file mode 100644 index 000000000..94e45f8f5 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/dialog_insecure_generic.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/layout/remote_security_issue_dialog.xml b/OpenKeychain/src/main/res/layout/remote_security_issue_dialog.xml new file mode 100644 index 000000000..9b81be222 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/remote_security_issue_dialog.xml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +