Merge pull request #2097 from open-keychain/security-problems
Security Warnings API
This commit is contained in:
@@ -859,6 +859,11 @@
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Keychain.Transparent"
|
||||
android:label="@string/app_name" />
|
||||
<activity
|
||||
android:name=".remote.ui.RemoteSecurityProblemDialogActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Keychain.Transparent"
|
||||
android:label="@string/app_name" />
|
||||
<activity
|
||||
android:name=".remote.ui.RemoteSelectPubKeyActivity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -19,16 +19,12 @@
|
||||
package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem;
|
||||
import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
@@ -40,7 +36,7 @@ public class DecryptVerifyResult extends InputPendingResult {
|
||||
OpenPgpSignatureResult mSignatureResult;
|
||||
OpenPgpDecryptionResult mDecryptionResult;
|
||||
OpenPgpMetadata mDecryptionMetadata;
|
||||
ArrayList<SecurityProblem> 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<SecurityProblem>) 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<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
|
||||
@@ -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<SecurityProblem> securityProblems) {
|
||||
if (securityProblems == null) {
|
||||
return;
|
||||
}
|
||||
if (mSecurityProblems == null) {
|
||||
mSecurityProblems = new ArrayList<>();
|
||||
}
|
||||
mSecurityProblems.addAll(securityProblems);
|
||||
}
|
||||
|
||||
public List<SecurityProblem> getSecurityProblems() {
|
||||
return mSecurityProblems != null ?
|
||||
Collections.unmodifiableList(mSecurityProblems) : Collections.<SecurityProblem>emptyList();
|
||||
public void setSecurityProblemResult(DecryptVerifySecurityProblem securityProblem) {
|
||||
mSecurityProblem = securityProblem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<SecurityProblem> securityProblems;
|
||||
|
||||
public void addSecurityProblem(SecurityProblem securityProblem) {
|
||||
if (securityProblems == null) {
|
||||
securityProblems = new ArrayList<>();
|
||||
}
|
||||
securityProblems.add(securityProblem);
|
||||
}
|
||||
|
||||
public List<SecurityProblem> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<SecurityProblem> 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<SecurityProblem> 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) {
|
||||
|
||||
@@ -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<PgpDecryptVerifyInp
|
||||
JcaSkipMarkerPGPObjectFactory plainFact;
|
||||
Object dataChunk;
|
||||
EncryptStreamResult esResult = null;
|
||||
DecryptVerifySecurityProblemBuilder securityProblemBuilder = new DecryptVerifySecurityProblemBuilder();
|
||||
{ // resolve encrypted (symmetric and asymmetric) packets
|
||||
JcaSkipMarkerPGPObjectFactory pgpF = new JcaSkipMarkerPGPObjectFactory(in);
|
||||
Object obj = pgpF.nextObject();
|
||||
@@ -322,15 +325,18 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
if (esResult.encryptionKeySecurityProblem != null) {
|
||||
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
||||
decryptionResultBuilder.addSecurityProblem(esResult.encryptionKeySecurityProblem);
|
||||
securityProblemBuilder.addEncryptionKeySecurityProblem(esResult.encryptionKeySecurityProblem);
|
||||
decryptionResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
// Check for insecure encryption algorithms!
|
||||
SymmetricAlgorithmProblem symmetricSecurityProblem =
|
||||
PgpSecurityConstants.checkSecureSymmetricAlgorithm(esResult.symmetricEncryptionAlgo);
|
||||
EncryptionAlgorithmProblem symmetricSecurityProblem =
|
||||
PgpSecurityConstants.checkSecureSymmetricAlgorithm(
|
||||
esResult.symmetricEncryptionAlgo, esResult.sessionKey);
|
||||
if (symmetricSecurityProblem != null) {
|
||||
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
||||
decryptionResultBuilder.addSecurityProblem(symmetricSecurityProblem);
|
||||
securityProblemBuilder.addSymmetricSecurityProblem(symmetricSecurityProblem);
|
||||
decryptionResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
plainFact = new JcaSkipMarkerPGPObjectFactory(esResult.cleartextStream);
|
||||
@@ -361,7 +367,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
plainFact = fact;
|
||||
}
|
||||
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mKeyRepository, input.getSenderAddress());
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(
|
||||
mKeyRepository, input.getSenderAddress(), securityProblemBuilder);
|
||||
if (signatureChecker.initializeOnePassSignature(dataChunk, log, indent +1)) {
|
||||
dataChunk = plainFact.nextObject();
|
||||
}
|
||||
@@ -536,7 +543,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
// Handle missing integrity protection like failed integrity protection!
|
||||
// The MDC packet can be stripped by an attacker!
|
||||
log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
|
||||
decryptionResultBuilder.addSecurityProblem(new MissingMdc());
|
||||
securityProblemBuilder.addSymmetricSecurityProblem(new MissingMdc(esResult.sessionKey));
|
||||
decryptionResultBuilder.setInsecure(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,8 +557,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
result.setCachedCryptoInputParcel(cryptoInput);
|
||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||
result.setDecryptionResult(decryptionResultBuilder.build());
|
||||
result.addSecurityProblems(signatureChecker.getSecurityProblems());
|
||||
result.addSecurityProblems(decryptionResultBuilder.getKeySecurityProblems());
|
||||
result.setSecurityProblemResult(securityProblemBuilder.build());
|
||||
result.setDecryptionMetadata(metadata);
|
||||
result.mOperationTime = opTime;
|
||||
|
||||
@@ -888,7 +895,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
updateProgress(R.string.progress_processing_signature, 60, 100);
|
||||
JcaSkipMarkerPGPObjectFactory pgpFact = new JcaSkipMarkerPGPObjectFactory(aIn);
|
||||
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mKeyRepository, input.getSenderAddress());
|
||||
DecryptVerifySecurityProblemBuilder securityProblemBuilder = new DecryptVerifySecurityProblemBuilder();
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mKeyRepository, input.getSenderAddress(),
|
||||
securityProblemBuilder);
|
||||
|
||||
Object o = pgpFact.nextObject();
|
||||
if (!signatureChecker.initializeSignature(o, log, indent+1)) {
|
||||
@@ -919,6 +928,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||
result.setDecryptionResult(
|
||||
new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
|
||||
result.setSecurityProblemResult(securityProblemBuilder.build());
|
||||
result.setDecryptionMetadata(metadata);
|
||||
return result;
|
||||
}
|
||||
@@ -943,7 +953,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
o = pgpFact.nextObject();
|
||||
}
|
||||
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mKeyRepository, input.getSenderAddress());
|
||||
DecryptVerifySecurityProblemBuilder securityProblemBuilder = new DecryptVerifySecurityProblemBuilder();
|
||||
PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mKeyRepository, input.getSenderAddress(),
|
||||
securityProblemBuilder);
|
||||
|
||||
if ( ! signatureChecker.initializeSignature(o, log, indent+1)) {
|
||||
log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
|
||||
@@ -994,6 +1006,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||
result.setSecurityProblemResult(securityProblemBuilder.build());
|
||||
result.setDecryptionResult(
|
||||
new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
|
||||
return result;
|
||||
|
||||
@@ -31,11 +31,11 @@ import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureHashAlgorithm;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureSymmetricAlgorithm;
|
||||
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.NotWhitelistedCurve;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.SymmetricAlgorithmProblem;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmProblem;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.UnidentifiedKeyProblem;
|
||||
|
||||
|
||||
@@ -74,9 +74,9 @@ public class PgpSecurityConstants {
|
||||
// CAMELLIA_256: not used widely
|
||||
));
|
||||
|
||||
public static SymmetricAlgorithmProblem checkSecureSymmetricAlgorithm(int id) {
|
||||
public static EncryptionAlgorithmProblem checkSecureSymmetricAlgorithm(int id, byte[] sessionKey) {
|
||||
if (!sSymmetricAlgorithmsWhitelist.contains(id)) {
|
||||
return new InsecureSymmetricAlgorithm(id);
|
||||
return new InsecureEncryptionAlgorithm(sessionKey, id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -107,9 +107,9 @@ public class PgpSecurityConstants {
|
||||
// SHA224: Not used widely, Yahoo argues against it
|
||||
));
|
||||
|
||||
public static InsecureHashAlgorithm checkSignatureAlgorithmForSecurityProblems(int hashAlgorithm) {
|
||||
static InsecureSigningAlgorithm checkSignatureAlgorithmForSecurityProblems(int hashAlgorithm) {
|
||||
if (!sHashAlgorithmsWhitelist.contains(hashAlgorithm)) {
|
||||
return new InsecureHashAlgorithm(hashAlgorithm);
|
||||
return new InsecureSigningAlgorithm(hashAlgorithm);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -27,28 +27,26 @@ import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
|
||||
public class PgpSignEncryptData implements Parcelable {
|
||||
|
||||
protected String mVersionHeader = null;
|
||||
protected boolean mEnableAsciiArmorOutput = false;
|
||||
protected int mCompressionAlgorithm = CompressionAlgorithmTags.UNCOMPRESSED;
|
||||
protected long[] mEncryptionMasterKeyIds = null;
|
||||
protected Passphrase mSymmetricPassphrase = null;
|
||||
protected int mSymmetricEncryptionAlgorithm = PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT;
|
||||
protected long mSignatureMasterKeyId = Constants.key.none;
|
||||
protected Long mSignatureSubKeyId = null;
|
||||
protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
|
||||
protected long mAdditionalEncryptId = Constants.key.none;
|
||||
protected String mCharset;
|
||||
protected boolean mCleartextSignature;
|
||||
protected boolean mDetachedSignature = false;
|
||||
protected boolean mHiddenRecipients = false;
|
||||
protected boolean mIntegrityProtected = true;
|
||||
protected boolean mAddBackupHeader = false;
|
||||
private String mVersionHeader = null;
|
||||
private boolean mEnableAsciiArmorOutput = false;
|
||||
private int mCompressionAlgorithm = CompressionAlgorithmTags.UNCOMPRESSED;
|
||||
private long[] mEncryptionMasterKeyIds = null;
|
||||
private Passphrase mSymmetricPassphrase = null;
|
||||
private int mSymmetricEncryptionAlgorithm = PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT;
|
||||
private long mSignatureMasterKeyId = Constants.key.none;
|
||||
private Long mSignatureSubKeyId = null;
|
||||
private int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
|
||||
private long mAdditionalEncryptId = Constants.key.none;
|
||||
private String mCharset;
|
||||
private boolean mCleartextSignature;
|
||||
private boolean mDetachedSignature = false;
|
||||
private boolean mHiddenRecipients = false;
|
||||
private boolean mAddBackupHeader = false;
|
||||
|
||||
public PgpSignEncryptData(){
|
||||
}
|
||||
|
||||
PgpSignEncryptData(Parcel source) {
|
||||
private PgpSignEncryptData(Parcel source) {
|
||||
ClassLoader loader = getClass().getClassLoader();
|
||||
|
||||
mVersionHeader = source.readString();
|
||||
@@ -65,7 +63,6 @@ public class PgpSignEncryptData implements Parcelable {
|
||||
mCleartextSignature = source.readInt() == 1;
|
||||
mDetachedSignature = source.readInt() == 1;
|
||||
mHiddenRecipients = source.readInt() == 1;
|
||||
mIntegrityProtected = source.readInt() == 1;
|
||||
mAddBackupHeader = source.readInt() == 1;
|
||||
}
|
||||
|
||||
@@ -95,7 +92,6 @@ public class PgpSignEncryptData implements Parcelable {
|
||||
dest.writeInt(mCleartextSignature ? 1 : 0);
|
||||
dest.writeInt(mDetachedSignature ? 1 : 0);
|
||||
dest.writeInt(mHiddenRecipients ? 1 : 0);
|
||||
dest.writeInt(mIntegrityProtected ? 1 : 0);
|
||||
dest.writeInt(mAddBackupHeader ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -220,18 +216,6 @@ public class PgpSignEncryptData implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isIntegrityProtected() {
|
||||
return mIntegrityProtected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only use for testing! Never disable integrity protection!
|
||||
*/
|
||||
public PgpSignEncryptData setIntegrityProtected(boolean integrityProtected) {
|
||||
this.mIntegrityProtected = integrityProtected;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptData setAddBackupHeader(boolean addBackupHeader) {
|
||||
this.mAddBackupHeader = addBackupHeader;
|
||||
return this;
|
||||
|
||||
@@ -321,7 +321,7 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa
|
||||
JcePGPDataEncryptorBuilder encryptorBuilder =
|
||||
new JcePGPDataEncryptorBuilder(algo)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
|
||||
.setWithIntegrityPacket(data.isIntegrityProtected());
|
||||
.setWithIntegrityPacket(true);
|
||||
|
||||
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SignatureException;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
@@ -37,11 +36,12 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureHashAlgorithm;
|
||||
import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem.DecryptVerifySecurityProblemBuilder;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureSigningAlgorithm;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
@@ -52,20 +52,24 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
class PgpSignatureChecker {
|
||||
|
||||
private final OpenPgpSignatureResultBuilder signatureResultBuilder;
|
||||
private final DecryptVerifySecurityProblemBuilder securityProblemBuilder;
|
||||
|
||||
private CanonicalizedPublicKey signingKey;
|
||||
|
||||
private int signatureIndex;
|
||||
PGPOnePassSignature onePassSignature;
|
||||
PGPSignature signature;
|
||||
private PGPOnePassSignature onePassSignature;
|
||||
private PGPSignature signature;
|
||||
|
||||
KeyRepository mKeyRepository;
|
||||
private KeyRepository mKeyRepository;
|
||||
|
||||
PgpSignatureChecker(KeyRepository keyRepository, String senderAddress) {
|
||||
PgpSignatureChecker(KeyRepository keyRepository, String senderAddress,
|
||||
DecryptVerifySecurityProblemBuilder securityProblemBuilder) {
|
||||
mKeyRepository = keyRepository;
|
||||
|
||||
signatureResultBuilder = new OpenPgpSignatureResultBuilder(keyRepository);
|
||||
signatureResultBuilder.setSenderAddress(senderAddress);
|
||||
|
||||
this.securityProblemBuilder = securityProblemBuilder;
|
||||
}
|
||||
|
||||
boolean initializeSignature(Object dataChunk, OperationLog log, int indent) throws PGPException {
|
||||
@@ -142,11 +146,12 @@ class PgpSignatureChecker {
|
||||
PgpSecurityConstants.checkForSecurityProblems(signingKey);
|
||||
if (keySecurityProblem != null) {
|
||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||
signatureResultBuilder.addSecurityProblem(keySecurityProblem);
|
||||
securityProblemBuilder.addSigningKeyProblem(keySecurityProblem);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
boolean isInitialized() {
|
||||
return signingKey != null;
|
||||
}
|
||||
|
||||
@@ -173,7 +178,7 @@ class PgpSignatureChecker {
|
||||
}
|
||||
}
|
||||
|
||||
public void findAvailableSignature(PGPSignatureList sigList) {
|
||||
private void findAvailableSignature(PGPSignatureList sigList) {
|
||||
// go through all signatures (should be just one), make sure we have
|
||||
// the key and it matches the one we’re looking for
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
@@ -238,11 +243,12 @@ class PgpSignatureChecker {
|
||||
}
|
||||
|
||||
// check for insecure hash algorithms
|
||||
InsecureHashAlgorithm signatureSecurityProblem =
|
||||
InsecureSigningAlgorithm signatureSecurityProblem =
|
||||
PgpSecurityConstants.checkSignatureAlgorithmForSecurityProblems(signature.getHashAlgorithm());
|
||||
if (signatureSecurityProblem != null) {
|
||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||
signatureResultBuilder.addSecurityProblem(signatureSecurityProblem);
|
||||
securityProblemBuilder.addSignatureSecurityProblem(signatureSecurityProblem);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
signatureResultBuilder.setSignatureTimestamp(signature.getCreationTime());
|
||||
@@ -275,11 +281,12 @@ class PgpSignatureChecker {
|
||||
}
|
||||
|
||||
// check for insecure hash algorithms
|
||||
InsecureHashAlgorithm signatureSecurityProblem =
|
||||
InsecureSigningAlgorithm signatureSecurityProblem =
|
||||
PgpSecurityConstants.checkSignatureAlgorithmForSecurityProblems(onePassSignature.getHashAlgorithm());
|
||||
if (signatureSecurityProblem != null) {
|
||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||
signatureResultBuilder.addSecurityProblem(signatureSecurityProblem);
|
||||
securityProblemBuilder.addSignatureSecurityProblem(signatureSecurityProblem);
|
||||
signatureResultBuilder.setInsecure(true);
|
||||
}
|
||||
|
||||
signatureResultBuilder.setSignatureTimestamp(messageSignature.getCreationTime());
|
||||
@@ -297,10 +304,6 @@ class PgpSignatureChecker {
|
||||
return signatureResultBuilder.build();
|
||||
}
|
||||
|
||||
public List<SecurityProblem> getSecurityProblems() {
|
||||
return signatureResultBuilder.getSecurityProblems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
* <p/>
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
51
OpenKeychain/src/main/res/layout/dialog_insecure_bitsize.xml
Normal file
51
OpenKeychain/src/main/res/layout/dialog_insecure_bitsize.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:showIn="@layout/remote_security_issue_dialog">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:text="Security Warning"
|
||||
android:id="@+id/dialog_title"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/api_register_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="The key this message was sent by is using an outdated algorithm, and is no longer considered secure!"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="The algorithm in use is DSA 1024 bit, which has been considered insecure since 2010."
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:text="Recommended Action"
|
||||
android:id="@+id/dialog_title_2"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/api_register_text_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="The key in use is insecure, and cannot be updated. To communicate securely, the sender must create a new key!"
|
||||
/>
|
||||
|
||||
</merge>
|
||||
|
||||
95
OpenKeychain/src/main/res/layout/dialog_insecure_generic.xml
Normal file
95
OpenKeychain/src/main/res/layout/dialog_insecure_generic.xml
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
||||
tools:showIn="@layout/remote_security_issue_dialog">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:text="@string/dialog_insecure_title"
|
||||
android:id="@+id/dialog_title"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_insecure_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
tools:text="The key this message was sent by is using an outdated algorithm, and is no longer considered secure!\n\nThe algorithm in use is DSA 1024 bit, which has been considered insecure since 2010."
|
||||
/>
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/dialog_insecure_secondary_layout"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
android:measureAllChildren="true"
|
||||
custom:initialView="1">
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:text="@string/dialog_insecure_recommend_title"
|
||||
android:id="@+id/dialog_title_2"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_insecure_recommend_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
tools:text="The key in use is insecure, and cannot be updated. To communicate securely, the sender must create a new key!"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:textColor="@color/android_red_light"
|
||||
android:text="@string/dialog_insecure_override_title"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:id="@+id/dialog_insecure_override_text"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
tools:text="@string/dialog_insecure_override"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||
|
||||
|
||||
</merge>
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:custom="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
tools:layout_marginTop="24dp"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:elevation="4dp"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:gravity="center_horizontal"
|
||||
tools:targetApi="lollipop">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:src="@drawable/link_24dp"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/icon_client_app"
|
||||
tools:src="@drawable/apps_k9"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- The parent properly determines the height of this scroll view, it is *not* useless -->
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:id="@+id/insecure_warning_layout"
|
||||
>
|
||||
|
||||
<include layout="@layout/dialog_insecure_generic" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/dialog_insecure_button_bar"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
tools:layout_marginBottom="50dp"
|
||||
custom:initialView="0">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end"
|
||||
android:padding="8dp"
|
||||
style="?buttonBarStyle">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_insecure_button_override"
|
||||
android:id="@+id/button_override"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
style="?buttonBarButtonStyle" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_insecure_button_undo"
|
||||
android:id="@+id/button_override_undo"
|
||||
android:visibility="gone"
|
||||
style="?buttonBarButtonStyle" />
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_insecure_button_view_key"
|
||||
android:id="@+id/button_view_key"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
style="?buttonBarButtonStyle" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_insecure_button_ok"
|
||||
android:id="@+id/button_allow"
|
||||
style="?buttonBarButtonStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end"
|
||||
android:padding="8dp"
|
||||
style="?buttonBarStyle">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Back"
|
||||
android:id="@+id/button_override_back"
|
||||
style="?buttonBarButtonStyle" />
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_insecure_button_override_confirm"
|
||||
android:id="@+id/button_override_confirm"
|
||||
style="?buttonBarButtonStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -8,6 +8,7 @@
|
||||
<declare-styleable name="FoldableLinearLayout">
|
||||
<attr name="foldedLabel" format="string" />
|
||||
<attr name="unFoldedLabel" format="string" />
|
||||
<attr name="initializeFolded" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- Taken from Matt Allen Password Strength View
|
||||
|
||||
@@ -1853,4 +1853,31 @@
|
||||
|
||||
<string name="key_expiry_text">"This key expired on <b>%1$s</b>."</string>
|
||||
|
||||
<string name="dialog_insecure_title">Security Warning</string>
|
||||
<string name="dialog_insecure_recommend_title">Recommended Action</string>
|
||||
<string name="insecure_encrypt_bitstrength">"The key you used to receive (decrypt) this message is outdated!\n\nThe algorithm (%s) is configured with a strength of 1024 bits or less, which has been considered insecure for general use since 2006."</string>
|
||||
<string name="insecure_encrypt_bitstrength_suggestion">The key in use is insecure, and cannot be updated. To communicate securely, you must create a new key!</string>
|
||||
<string name="insecure_sign_bitstrength">"The key that sent (signed) this message is outdated!\n\nThe algorithm (%s) is configured with a strength of 1024 bits or less, which has been considered insecure for general use since 2006."</string>
|
||||
<string name="insecure_sign_bitstrength_suggestion">The key in use is insecure, and cannot be updated. To communicate securely, the sender must create a new key!</string>
|
||||
<string name="insecure_encrypt_not_whitelisted_curve">"The key that received (decrypted) this message is using the elliptic curve with OID %1$s, which is not whitelisted!"</string>
|
||||
<string name="insecure_sign_not_whitelisted_curve">"The key this message was sent (signed) from is using the elliptic curve with OID %1$s, which is not whitelisted!"</string>
|
||||
<string name="insecure_encrypt_unidentified">"There is an unidentified security problem with the key used to receive (decrypyt) this message!"</string>
|
||||
<string name="insecure_sign_unidentified">"There is an unidentified security problem with the key used to send (sign) this message!"</string>
|
||||
<string name="insecure_report_suggestion">"This might be a problem in OpenKeychain, please report on our issue tracker!"</string>
|
||||
|
||||
<string name="insecure_mdc">"This message was not signed, and did also not contain a Modification Detection Code (MDC). It may have been modified by an attacker!"</string>
|
||||
<string name="insecure_mdc_suggestion">"A missing MDC is a problem in the sending software, or an attack. For secure end-to-end communication, messages should also be signed by the sender!"</string>
|
||||
|
||||
<string name="insecure_symmetric_algo">"This message was encrypted using the symmetric %s algorithm. This is considered insecure, or at least exotic!"</string>
|
||||
<string name="insecure_hash_algo">"This message was signed using the %s hashing algorithm. This is considered insecure, or at least exotic!"</string>
|
||||
<string name="dialog_insecure_override">If you don\'t want to be warned about this specific security problem in the future, you can suppress this warning.</string>
|
||||
<string name="dialog_insecure_override_title">Suppress this warning</string>
|
||||
<string name="dialog_insecure_override_ok_title">Warning suppressed</string>
|
||||
<string name="dialog_insecure_override_ok">The security warning (for this key/message) will not be shown again in the future.</string>
|
||||
<string name="dialog_insecure_button_override">Suppress</string>
|
||||
<string name="dialog_insecure_button_override_confirm">Suppress (%d)</string>
|
||||
<string name="dialog_insecure_button_undo">Un-Suppress</string>
|
||||
<string name="dialog_insecure_button_view_key">View Key</string>
|
||||
<string name="dialog_insecure_button_ok">Got it</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
* Copyright (C) 2014-2017 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* 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
|
||||
@@ -18,11 +18,23 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.tools.ant.util.StringUtils;
|
||||
import org.bouncycastle.bcpg.BCPGInputStream;
|
||||
import org.bouncycastle.bcpg.Packet;
|
||||
import org.bouncycastle.bcpg.PacketTags;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
|
||||
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.Assert;
|
||||
@@ -38,13 +50,13 @@ import org.robolectric.shadows.ShadowLog;
|
||||
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureEncryptionAlgorithm;
|
||||
import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc;
|
||||
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
|
||||
@@ -55,28 +67,18 @@ import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
import org.sufficientlysecure.keychain.util.TestingUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.hamcrest.core.AnyOf.anyOf;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(KeychainTestRunner.class)
|
||||
public class PgpEncryptDecryptTest {
|
||||
|
||||
static Passphrase mSymmetricPassphrase = TestingUtils.genPassphrase(true);
|
||||
|
||||
static UncachedKeyRing mStaticRing1, mStaticRing2, mStaticRingInsecure;
|
||||
static Passphrase mKeyPhrase1 = TestingUtils.genPassphrase(true);
|
||||
static Passphrase mKeyPhrase2 = TestingUtils.genPassphrase(true);
|
||||
// static Passphrase mKeyPhraseInsecure = TestingUtils.genPassphrase(true);
|
||||
static Passphrase mKeyPhrase1, mKeyPhrase2;
|
||||
|
||||
static PrintStream oldShadowStream;
|
||||
|
||||
@@ -86,65 +88,64 @@ public class PgpEncryptDecryptTest {
|
||||
oldShadowStream = ShadowLog.stream;
|
||||
// ShadowLog.stream = System.out;
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
/* generation parameters:
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDH, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDH, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
parcel.mAddUserIds.add("bloom");
|
||||
parcel.setNewUnlock(new ChangeUnlockParcel(mKeyPhrase1));
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDH, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDH, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
parcel.mAddUserIds.add("bloom");
|
||||
parcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase("RsKrW^raOPcnQ=ZJr-pP")));
|
||||
|
||||
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
Assert.assertTrue("initial test key creation must succeed", result.success());
|
||||
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
|
||||
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
*/
|
||||
|
||||
mStaticRing1 = result.getRing();
|
||||
}
|
||||
mKeyPhrase1 = new Passphrase("RsKrW^raOPcnQ=ZJr-pP");
|
||||
mStaticRing1 = KeyringTestingHelper.readRingFromResource("/test-keys/encrypt_decrypt_key_1.sec");
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDH, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
parcel.mAddUserIds.add("belle");
|
||||
parcel.setNewUnlock(new ChangeUnlockParcel(mKeyPhrase2));
|
||||
/* generation:
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.ECDH, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
parcel.mAddUserIds.add("belle");
|
||||
parcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase("x")));
|
||||
|
||||
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
Assert.assertTrue("initial test key creation must succeed", result.success());
|
||||
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
|
||||
PgpKeyOperation op = new PgpKeyOperation(new ProgressScaler());
|
||||
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
new FileOutputStream("/tmp/key.sec").write(result.getRing().getEncoded());
|
||||
*/
|
||||
|
||||
mStaticRing2 = result.getRing();
|
||||
}
|
||||
mKeyPhrase2 = new Passphrase("x");
|
||||
mStaticRing2 = KeyringTestingHelper.readRingFromResource("/test-keys/encrypt_decrypt_key_2.sec");
|
||||
|
||||
// {
|
||||
// // insecure (1024 bit) RSA key
|
||||
// SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
// parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
// Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
// parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
// Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
// parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
// Algorithm.RSA, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
// parcel.mAddUserIds.add("eve");
|
||||
// parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhraseInsecure);
|
||||
//
|
||||
// PgpEditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
// Assert.assertTrue("initial test key creation must succeed", result.success());
|
||||
// Assert.assertNotNull("initial test key creation must succeed", result.getRing());
|
||||
//
|
||||
// mStaticRingInsecure = result.getRing();
|
||||
// }
|
||||
/* generation parameters (insecure key, requires removal of of security checks!):
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
|
||||
parcel.mAddUserIds.add("eve");
|
||||
parcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase("")));
|
||||
|
||||
PgpEditKeyResult result = new PgpKeyOperation(new ProgressScaler()).createSecretKeyRing(parcel);
|
||||
Assert.assertTrue("initial test key creation must succeed", result.success());
|
||||
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
|
||||
new FileOutputStream("/tmp/key.sec").write(result.getRing().getEncoded());
|
||||
*/
|
||||
|
||||
mStaticRingInsecure = KeyringTestingHelper.readRingFromResource("/test-keys/encrypt_decrypt_key_insecure.sec");
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -157,6 +158,7 @@ public class PgpEncryptDecryptTest {
|
||||
|
||||
databaseInteractor.saveSecretKeyRing(mStaticRing1, new ProgressScaler());
|
||||
databaseInteractor.saveSecretKeyRing(mStaticRing2, new ProgressScaler());
|
||||
databaseInteractor.saveSecretKeyRing(mStaticRingInsecure, new ProgressScaler());
|
||||
|
||||
// ok NOW log verbosely!
|
||||
ShadowLog.stream = System.out;
|
||||
@@ -420,7 +422,7 @@ public class PgpEncryptDecryptTest {
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
PgpSignEncryptData pgpData = new PgpSignEncryptData();
|
||||
PgpSignEncryptData pgpData = new PgpSignEncryptData();
|
||||
// only sign, as cleartext
|
||||
pgpData.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId());
|
||||
pgpData.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1));
|
||||
@@ -997,76 +999,73 @@ public class PgpEncryptDecryptTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsymmetricInsecureEncryptDecrypt() {
|
||||
public void testAsymmetricSymmetricDesDecrypt() throws Exception {
|
||||
InputStream in = getResourceAsStream("/test-ciphertexts/algo_des.pgp.asc");
|
||||
String plaintext = "dies ist ein plaintext ☭";
|
||||
|
||||
// insecure symmetric algo
|
||||
subtestInsecureEncryptDecrypt(mStaticRing1, mKeyPhrase1,
|
||||
PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.DES, true);
|
||||
// don't use MDC
|
||||
subtestInsecureEncryptDecrypt(mStaticRing1, mKeyPhrase1,
|
||||
PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.AES_256, false);
|
||||
// TODO: test not working!
|
||||
// insecure key (1024 bit RSA)
|
||||
// subtestInsecureEncryptDecrypt(mStaticRingInsecure, mKeyPhraseInsecure,
|
||||
// PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.AES_256, true);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
PgpDecryptVerifyOperation op = operationWithFakePassphraseCache(null, null, null);
|
||||
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
|
||||
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(mKeyPhrase1), data, out);
|
||||
|
||||
|
||||
Assert.assertTrue(result.success());
|
||||
Assert.assertArrayEquals(out.toByteArray(), plaintext.getBytes());
|
||||
Assert.assertEquals(OpenPgpDecryptionResult.RESULT_INSECURE, result.getDecryptionResult().getResult());
|
||||
Assert.assertEquals(OpenPgpSignatureResult.RESULT_NO_SIGNATURE, result.getSignatureResult().getResult());
|
||||
|
||||
InsecureEncryptionAlgorithm symmetricSecurityProblem =
|
||||
(InsecureEncryptionAlgorithm) result.getSecurityProblem().symmetricSecurityProblem;
|
||||
Assert.assertEquals((symmetricSecurityProblem).symmetricAlgorithm, SymmetricKeyAlgorithmTags.DES);
|
||||
}
|
||||
|
||||
private void subtestInsecureEncryptDecrypt(UncachedKeyRing key, Passphrase passphrase,
|
||||
int algorithm, boolean isIntegrityProtected) {
|
||||
String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);
|
||||
byte[] ciphertext;
|
||||
public void testAsymmetricNoMdcDecrypt() throws Exception {
|
||||
InputStream in = getResourceAsStream("/test-ciphertexts/no_mdc.pgp.asc");
|
||||
String plaintext = "dies ist ein plaintext ☭";
|
||||
|
||||
{ // encrypt data with insecure key
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes());
|
||||
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application,
|
||||
KeyWritableRepository.createDatabaseReadWriteInteractor(RuntimeEnvironment.application), null);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
PgpDecryptVerifyOperation op = operationWithFakePassphraseCache(null, null, null);
|
||||
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
|
||||
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(mKeyPhrase1), data, out);
|
||||
|
||||
PgpSignEncryptData pgpData = new PgpSignEncryptData();
|
||||
pgpData.setEncryptionMasterKeyIds(new long[]{key.getMasterKeyId()})
|
||||
.setSymmetricEncryptionAlgorithm(algorithm)
|
||||
.setIntegrityProtected(isIntegrityProtected);
|
||||
|
||||
PgpSignEncryptInputParcel input = new PgpSignEncryptInputParcel(pgpData);
|
||||
Assert.assertTrue(result.success());
|
||||
Assert.assertArrayEquals(out.toByteArray(), plaintext.getBytes());
|
||||
Assert.assertEquals(OpenPgpDecryptionResult.RESULT_INSECURE, result.getDecryptionResult().getResult());
|
||||
Assert.assertEquals(OpenPgpSignatureResult.RESULT_NO_SIGNATURE, result.getSignatureResult().getResult());
|
||||
|
||||
PgpSignEncryptResult result = op.execute(input, new CryptoInputParcel(new Date()),
|
||||
data, out);
|
||||
Assert.assertTrue("encryption must succeed", result.success());
|
||||
Assert.assertTrue(result.getSecurityProblem().symmetricSecurityProblem instanceof MissingMdc);
|
||||
}
|
||||
|
||||
ciphertext = out.toByteArray();
|
||||
}
|
||||
public void testAsymmetricRsa1024Decrypt() throws Exception {
|
||||
InputStream in = getResourceAsStream("/test-ciphertexts/rsa_1024.pgp.asc");
|
||||
String plaintext = "dies ist ein plaintext ☭";
|
||||
|
||||
{ // decryption with provided passphrase should yield insecure status
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
|
||||
InputData data = new InputData(in, in.available());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
PgpDecryptVerifyOperation op = operationWithFakePassphraseCache(null, null, null);
|
||||
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
|
||||
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(passphrase), data, out);
|
||||
PgpDecryptVerifyOperation op = operationWithFakePassphraseCache(null, null, null);
|
||||
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
|
||||
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
|
||||
|
||||
Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with provided passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
Assert.assertEquals("decryptionResult should be RESULT_INSECURE",
|
||||
OpenPgpDecryptionResult.RESULT_INSECURE, result.getDecryptionResult().getResult());
|
||||
Assert.assertEquals("signatureResult should be RESULT_NO_SIGNATURE",
|
||||
OpenPgpSignatureResult.RESULT_NO_SIGNATURE, result.getSignatureResult().getResult());
|
||||
|
||||
CryptoInputParcel cryptoInput = result.getCachedCryptoInputParcel();
|
||||
Assert.assertEquals("must have one cached session key",
|
||||
1, cryptoInput.getCryptoData().size());
|
||||
Assert.assertTrue(result.success());
|
||||
Assert.assertArrayEquals(out.toByteArray(), plaintext.getBytes());
|
||||
Assert.assertEquals(OpenPgpDecryptionResult.RESULT_INSECURE, result.getDecryptionResult().getResult());
|
||||
Assert.assertEquals(OpenPgpSignatureResult.RESULT_NO_SIGNATURE, result.getSignatureResult().getResult());
|
||||
|
||||
OpenPgpMetadata metadata = result.getDecryptionMetadata();
|
||||
Assert.assertEquals("filesize must be correct",
|
||||
out.toByteArray().length, metadata.getOriginalSize());
|
||||
|
||||
}
|
||||
InsecureBitStrength encryptionKeySecurityProblem =
|
||||
(InsecureBitStrength) result.getSecurityProblem().encryptionKeySecurityProblem;
|
||||
Assert.assertEquals(mStaticRingInsecure.getMasterKeyId(), encryptionKeySecurityProblem.masterKeyId);
|
||||
Assert.assertEquals(PublicKeyAlgorithmTags.RSA_ENCRYPT, encryptionKeySecurityProblem.algorithm);
|
||||
Assert.assertEquals(1024, encryptionKeySecurityProblem.bitStrength);
|
||||
}
|
||||
|
||||
private PgpDecryptVerifyOperation operationWithFakePassphraseCache(
|
||||
@@ -1092,4 +1091,34 @@ public class PgpEncryptDecryptTest {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static InputStream getResourceAsStream(String name) {
|
||||
return PgpEncryptDecryptTest.class.getResourceAsStream(name);
|
||||
}
|
||||
|
||||
/* skeleton for generating test data
|
||||
@Test
|
||||
public void generateData() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream("dies ist ein plaintext ☭".getBytes());
|
||||
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application,
|
||||
KeyWritableRepository.createDatabaseReadWriteInteractor(RuntimeEnvironment.application), null);
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
PgpSignEncryptData pgpData = new PgpSignEncryptData();
|
||||
pgpData.setEncryptionMasterKeyIds(new long[]{ mStaticRingInsecure.getMasterKeyId()});
|
||||
|
||||
PgpSignEncryptInputParcel input = new PgpSignEncryptInputParcel(pgpData);
|
||||
|
||||
PgpSignEncryptResult result = op.execute(input, new CryptoInputParcel(new Date()),
|
||||
data, out);
|
||||
Assert.assertTrue("encryption must succeed", result.success());
|
||||
|
||||
ArmoredOutputStream armoredOutputStream = new ArmoredOutputStream(new FileOutputStream("/tmp/rsa_1024.pgp.asc"));
|
||||
armoredOutputStream.write(out.toByteArray());
|
||||
armoredOutputStream.close();
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class UncachedKeyringTest {
|
||||
readRingFromResource("/test-keys/broken_cert_version.asc");
|
||||
}
|
||||
|
||||
UncachedKeyRing readRingFromResource(String name) throws Throwable {
|
||||
private UncachedKeyRing readRingFromResource(String name) throws Throwable {
|
||||
return UncachedKeyRing.fromStream(UncachedKeyringTest.class.getResourceAsStream(name)).next();
|
||||
}
|
||||
|
||||
|
||||
@@ -364,4 +364,7 @@ public class KeyringTestingHelper {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static UncachedKeyRing readRingFromResource(String name) throws Exception {
|
||||
return UncachedKeyRing.fromStream(KeyringTestingHelper.class.getResourceAsStream(name)).next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Testcase: encrypted using DES (should be marked insecure)
|
||||
Plaintext: 'dies ist ein plaintext ☭'
|
||||
|
||||
hH4DGQB0mNo0qa0SAgME9glrSS/q7JAz+wh0WoOydUAVsCS9jaMRx8gBWrx8NJB4
|
||||
OcEZnYiKU5Nu14qDkPKpqUwq0l/D2ykvfk3oDerb2jDL2M9j+sy/lWPeaSd7MbWO
|
||||
8cVKqMaNkXFSDp4V/wRTrj+xnDaZZ2v/OUT3jWZuneGEfgNG/bBLBjduwxICAwQS
|
||||
Jr1sGq9DUNHkhDXX+t9Y7Z1idE7kr/skjziBP3L1RhIavVtU6sEjDOs/3IAZUgQx
|
||||
RnU7ITIw0dVrVMPCZ0zWMEG5WZCBCtqdPHfXyESTqR891e5uqAtLf9Og7LIPkwiN
|
||||
GXNc3r+dm5tcqfALmmwHttJDAYW22pFTOf52358ENWEBIgePFiKXImAXjS/VAVAV
|
||||
t1OnpO81+BnJDBTrbzbXYy/2wxFMwnqt6HiVLTSTB93p61VFAw==
|
||||
=4WKW
|
||||
-----END PGP MESSAGE-----
|
||||
@@ -0,0 +1,13 @@
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Testcase: encrypted and unsigned, without mdc
|
||||
Plaintext: 'dies ist ein plaintext ☭'
|
||||
|
||||
hH4DGQB0mNo0qa0SAgMEThtzE4S/Y1z03qmQ05p0uydye5zzgFSyefNFmvneu1rh
|
||||
m7VX0bXfQfnK3QlKtONFzhgzmZLIdo8q6JNP1CtkfTCOjRgcA6sdZA6PbPgAXgoq
|
||||
CY1Dpt69K+TOzD37jxpzbEyB2S3yEO9VjxJVo6u0JbSEfgNG/bBLBjduwxICAwSU
|
||||
sILMkNnz3Qz9kSrjNwXR/PcQSdOdt9EI4ei26+Fp+eC6txTo8DC7YMcdXe9XqXoe
|
||||
xX5DvjhoBOQvCjmeCbeXMDxflR/ygybvZCyzrVxgTs7kkjUalI16saXjkelcF3Kt
|
||||
WVnLRbVs0+SWMwLt+Nf6HMk0ieELGF6MMR+2AXNC0VY3b3WwJAMWZFsz9P4VG7rP
|
||||
YiHtyfyVSnXiyaKPzevD3TKaZtse/g==
|
||||
=sUCU
|
||||
-----END PGP MESSAGE-----
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Testcase: 1024 bit rsa (should be marked insecure)
|
||||
Plaintext: 'dies ist ein plaintext ☭'
|
||||
|
||||
hIsDUIIrlXQHiJEBA/YiKQH9teIvmGQ057tyUsVplsM68phoS6xG9aMP1DXZsHB3
|
||||
a0nzPCJ/s/9Ct9X1VwrFv6gKMZVAjDOvizDpc8Q+R88BMyXxcxTJjg7x4ZIGxLAL
|
||||
7p7aEBJj243SWgu9SVQ5I6zF4M0RZBb50Q5EVIjeYhI2HN3CZYQTllgZySgg0ksB
|
||||
rHEsemlxXivKscIkhYSAlkx9pGQ+eEIXtgzsFacbYuk2rVAztaO7NQFu/Zrnbpyn
|
||||
UMdSEVfpIBLLHep71wA4P55fxBubAJ/YUvw=
|
||||
=v0D8
|
||||
-----END PGP MESSAGE-----
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
extern/openpgp-api-lib
vendored
2
extern/openpgp-api-lib
vendored
Submodule extern/openpgp-api-lib updated: f28cb92944...395fe837a2
Reference in New Issue
Block a user