Merge pull request #2097 from open-keychain/security-problems

Security Warnings API
This commit is contained in:
Dominik Schürmann
2017-05-17 13:52:56 +03:00
committed by GitHub
35 changed files with 1632 additions and 313 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 were 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
*/

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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!

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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) {

View File

@@ -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();
}
});
}
}
}

View File

@@ -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();
}
}

View File

@@ -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/>

View File

@@ -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
*