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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/attr.xml b/OpenKeychain/src/main/res/values/attr.xml
index 7e8b98343..50f2126a5 100644
--- a/OpenKeychain/src/main/res/values/attr.xml
+++ b/OpenKeychain/src/main/res/values/attr.xml
@@ -8,6 +8,7 @@
+