@@ -18,11 +18,17 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.operations.results;
|
package org.sufficientlysecure.keychain.operations.results;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
||||||
import org.openintents.openpgp.OpenPgpMetadata;
|
import org.openintents.openpgp.OpenPgpMetadata;
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
|
|
||||||
@@ -34,6 +40,7 @@ public class DecryptVerifyResult extends InputPendingResult {
|
|||||||
OpenPgpSignatureResult mSignatureResult;
|
OpenPgpSignatureResult mSignatureResult;
|
||||||
OpenPgpDecryptionResult mDecryptionResult;
|
OpenPgpDecryptionResult mDecryptionResult;
|
||||||
OpenPgpMetadata mDecryptionMetadata;
|
OpenPgpMetadata mDecryptionMetadata;
|
||||||
|
ArrayList<SecurityProblem> mSecurityProblems;
|
||||||
|
|
||||||
CryptoInputParcel mCachedCryptoInputParcel;
|
CryptoInputParcel mCachedCryptoInputParcel;
|
||||||
|
|
||||||
@@ -65,6 +72,8 @@ public class DecryptVerifyResult extends InputPendingResult {
|
|||||||
mDecryptionMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
|
mDecryptionMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
|
||||||
mCachedCryptoInputParcel = source.readParcelable(CryptoInputParcel.class.getClassLoader());
|
mCachedCryptoInputParcel = source.readParcelable(CryptoInputParcel.class.getClassLoader());
|
||||||
mSkippedDisallowedKeys = source.createLongArray();
|
mSkippedDisallowedKeys = source.createLongArray();
|
||||||
|
|
||||||
|
mSecurityProblems = (ArrayList<SecurityProblem>) source.readSerializable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -127,6 +136,8 @@ public class DecryptVerifyResult extends InputPendingResult {
|
|||||||
dest.writeParcelable(mDecryptionMetadata, flags);
|
dest.writeParcelable(mDecryptionMetadata, flags);
|
||||||
dest.writeParcelable(mCachedCryptoInputParcel, flags);
|
dest.writeParcelable(mCachedCryptoInputParcel, flags);
|
||||||
dest.writeLongArray(mSkippedDisallowedKeys);
|
dest.writeLongArray(mSkippedDisallowedKeys);
|
||||||
|
|
||||||
|
dest.writeSerializable(mSecurityProblems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
|
public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
|
||||||
@@ -139,4 +150,28 @@ public class DecryptVerifyResult extends InputPendingResult {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public void addSecurityProblem(SecurityProblem securityProblem) {
|
||||||
|
if (securityProblem == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mSecurityProblems == null) {
|
||||||
|
mSecurityProblems = new ArrayList<>();
|
||||||
|
}
|
||||||
|
mSecurityProblems.add(securityProblem);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,15 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
||||||
import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
|
import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
|
||||||
@@ -36,14 +45,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
|||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.interfaces.ECPublicKey;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
/** Wrapper for a PGPPublicKey.
|
/** Wrapper for a PGPPublicKey.
|
||||||
*
|
*
|
||||||
* The methods implemented in this class are a thin layer over
|
* The methods implemented in this class are a thin layer over
|
||||||
@@ -141,7 +142,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecure() {
|
public boolean isSecure() {
|
||||||
return PgpSecurityConstants.isSecureKey(this);
|
return PgpSecurityConstants.checkForSecurityProblems(this) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getValidSeconds() {
|
public long getValidSeconds() {
|
||||||
|
|||||||
@@ -121,6 +121,29 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
|||||||
return this != UNAVAILABLE && this != GNU_DUMMY;
|
return this != UNAVAILABLE && this != GNU_DUMMY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Compares by "usability", which basically compares how independently usable
|
||||||
|
* two SecretKeyTypes are. The order is roughly this:
|
||||||
|
*
|
||||||
|
* empty passphrase < passphrase/others < divert < stripped
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public int compareUsability(SecretKeyType other) {
|
||||||
|
// if one is usable but the other isn't, the usable one comes first
|
||||||
|
if (isUsable() ^ other.isUsable()) {
|
||||||
|
return isUsable() ? -1 : 1;
|
||||||
|
}
|
||||||
|
// if one is a divert-to-card but the other isn't, the non-divert one comes first
|
||||||
|
if ((this == DIVERT_TO_CARD) ^ (other == DIVERT_TO_CARD)) {
|
||||||
|
return this != DIVERT_TO_CARD ? -1 : 1;
|
||||||
|
}
|
||||||
|
// if one requires a passphrase but another doesn't, the one without a passphrase comes first
|
||||||
|
if ((this == PASSPHRASE_EMPTY) ^ (other == PASSPHRASE_EMPTY)) {
|
||||||
|
return this == PASSPHRASE_EMPTY ? -1 : 1;
|
||||||
|
}
|
||||||
|
// all other (current) cases are equal
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This method returns the SecretKeyType for this secret key, testing for an empty
|
/** This method returns the SecretKeyType for this secret key, testing for an empty
|
||||||
|
|||||||
@@ -17,35 +17,49 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.SymmetricAlgorithmProblem;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
public class OpenPgpDecryptionResultBuilder {
|
public class OpenPgpDecryptionResultBuilder {
|
||||||
|
|
||||||
// builder
|
// builder
|
||||||
private boolean mInsecure = false;
|
private boolean isEncrypted = false;
|
||||||
private boolean mEncrypted = false;
|
|
||||||
private byte[] sessionKey;
|
private byte[] sessionKey;
|
||||||
private byte[] decryptedSessionKey;
|
private byte[] decryptedSessionKey;
|
||||||
|
private ArrayList<SecurityProblem> securityProblems;
|
||||||
|
|
||||||
public void setInsecure(boolean insecure) {
|
public void addSecurityProblem(SecurityProblem securityProblem) {
|
||||||
this.mInsecure = insecure;
|
if (securityProblems == null) {
|
||||||
|
securityProblems = new ArrayList<>();
|
||||||
|
}
|
||||||
|
securityProblems.add(securityProblem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SecurityProblem> getKeySecurityProblems() {
|
||||||
|
return securityProblems != null ? Collections.unmodifiableList(securityProblems) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEncrypted(boolean encrypted) {
|
public void setEncrypted(boolean encrypted) {
|
||||||
this.mEncrypted = encrypted;
|
this.isEncrypted = encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpDecryptionResult build() {
|
public OpenPgpDecryptionResult build() {
|
||||||
if (mInsecure) {
|
if (securityProblems != null && !securityProblems.isEmpty()) {
|
||||||
Log.d(Constants.TAG, "RESULT_INSECURE");
|
Log.d(Constants.TAG, "RESULT_INSECURE");
|
||||||
return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_INSECURE, sessionKey, decryptedSessionKey);
|
return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_INSECURE, sessionKey, decryptedSessionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mEncrypted) {
|
if (isEncrypted) {
|
||||||
Log.d(Constants.TAG, "RESULT_ENCRYPTED");
|
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");
|
Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED");
|
||||||
@@ -60,4 +74,5 @@ public class OpenPgpDecryptionResultBuilder {
|
|||||||
this.sessionKey = sessionKey;
|
this.sessionKey = sessionKey;
|
||||||
this.decryptedSessionKey = decryptedSessionKey;
|
this.decryptedSessionKey = decryptedSessionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult;
|
||||||
@@ -53,9 +55,9 @@ public class OpenPgpSignatureResultBuilder {
|
|||||||
private boolean mIsSignatureKeyCertified = false;
|
private boolean mIsSignatureKeyCertified = false;
|
||||||
private boolean mIsKeyRevoked = false;
|
private boolean mIsKeyRevoked = false;
|
||||||
private boolean mIsKeyExpired = false;
|
private boolean mIsKeyExpired = false;
|
||||||
private boolean mInsecure = false;
|
|
||||||
private String mSenderAddress;
|
private String mSenderAddress;
|
||||||
private Date mSignatureTimestamp;
|
private Date mSignatureTimestamp;
|
||||||
|
private ArrayList<SecurityProblem> mSecurityProblems;
|
||||||
|
|
||||||
public OpenPgpSignatureResultBuilder(KeyRepository keyRepository) {
|
public OpenPgpSignatureResultBuilder(KeyRepository keyRepository) {
|
||||||
this.mKeyRepository = keyRepository;
|
this.mKeyRepository = keyRepository;
|
||||||
@@ -81,8 +83,15 @@ public class OpenPgpSignatureResultBuilder {
|
|||||||
this.mValidSignature = validSignature;
|
this.mValidSignature = validSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInsecure(boolean insecure) {
|
public void addSecurityProblem(SecurityProblem securityProblem) {
|
||||||
this.mInsecure = insecure;
|
if (mSecurityProblems == null) {
|
||||||
|
mSecurityProblems = new ArrayList<>();
|
||||||
|
}
|
||||||
|
mSecurityProblems.add(securityProblem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SecurityProblem> getSecurityProblems() {
|
||||||
|
return mSecurityProblems != null ? Collections.unmodifiableList(mSecurityProblems) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSignatureKeyCertified(boolean isSignatureKeyCertified) {
|
public void setSignatureKeyCertified(boolean isSignatureKeyCertified) {
|
||||||
@@ -106,10 +115,6 @@ public class OpenPgpSignatureResultBuilder {
|
|||||||
this.mConfirmedUserIds = confirmedUserIds;
|
this.mConfirmedUserIds = confirmedUserIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInsecure() {
|
|
||||||
return mInsecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initValid(CanonicalizedPublicKey signingKey) {
|
public void initValid(CanonicalizedPublicKey signingKey) {
|
||||||
setSignatureAvailable(true);
|
setSignatureAvailable(true);
|
||||||
setKnownKey(true);
|
setKnownKey(true);
|
||||||
@@ -184,7 +189,7 @@ public class OpenPgpSignatureResultBuilder {
|
|||||||
} else if (mIsKeyExpired) {
|
} else if (mIsKeyExpired) {
|
||||||
Log.d(Constants.TAG, "RESULT_INVALID_KEY_EXPIRED");
|
Log.d(Constants.TAG, "RESULT_INVALID_KEY_EXPIRED");
|
||||||
signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED;
|
signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED;
|
||||||
} else if (mInsecure) {
|
} else if (mSecurityProblems != null && !mSecurityProblems.isEmpty()) {
|
||||||
Log.d(Constants.TAG, "RESULT_INVALID_INSECURE");
|
Log.d(Constants.TAG, "RESULT_INVALID_INSECURE");
|
||||||
signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE;
|
signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE;
|
||||||
} else if (mIsSignatureKeyCertified) {
|
} else if (mIsSignatureKeyCertified) {
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||||
|
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.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||||
@@ -211,7 +214,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
int symmetricEncryptionAlgo = 0;
|
int symmetricEncryptionAlgo = 0;
|
||||||
|
|
||||||
HashSet<Long> skippedDisallowedEncryptionKeys = new HashSet<>();
|
HashSet<Long> skippedDisallowedEncryptionKeys = new HashSet<>();
|
||||||
boolean insecureEncryptionKey = false;
|
KeySecurityProblem encryptionKeySecurityProblem = null;
|
||||||
|
|
||||||
// convenience method to return with error
|
// convenience method to return with error
|
||||||
public EncryptStreamResult with(DecryptVerifyResult result) {
|
public EncryptStreamResult with(DecryptVerifyResult result) {
|
||||||
@@ -317,15 +320,17 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
decryptionResultBuilder.setSessionKey(esResult.sessionKey, esResult.decryptedSessionKey);
|
decryptionResultBuilder.setSessionKey(esResult.sessionKey, esResult.decryptedSessionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esResult.insecureEncryptionKey) {
|
if (esResult.encryptionKeySecurityProblem != null) {
|
||||||
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
||||||
decryptionResultBuilder.setInsecure(true);
|
decryptionResultBuilder.addSecurityProblem(esResult.encryptionKeySecurityProblem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for insecure encryption algorithms!
|
// Check for insecure encryption algorithms!
|
||||||
if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(esResult.symmetricEncryptionAlgo)) {
|
SymmetricAlgorithmProblem symmetricSecurityProblem =
|
||||||
|
PgpSecurityConstants.checkSecureSymmetricAlgorithm(esResult.symmetricEncryptionAlgo);
|
||||||
|
if (symmetricSecurityProblem != null) {
|
||||||
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
||||||
decryptionResultBuilder.setInsecure(true);
|
decryptionResultBuilder.addSecurityProblem(symmetricSecurityProblem);
|
||||||
}
|
}
|
||||||
|
|
||||||
plainFact = new JcaSkipMarkerPGPObjectFactory(esResult.cleartextStream);
|
plainFact = new JcaSkipMarkerPGPObjectFactory(esResult.cleartextStream);
|
||||||
@@ -531,7 +536,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
// Handle missing integrity protection like failed integrity protection!
|
// Handle missing integrity protection like failed integrity protection!
|
||||||
// The MDC packet can be stripped by an attacker!
|
// The MDC packet can be stripped by an attacker!
|
||||||
log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
|
log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
|
||||||
decryptionResultBuilder.setInsecure(true);
|
decryptionResultBuilder.addSecurityProblem(new MissingMdc());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,10 +546,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
|
|
||||||
// Return a positive result, with metadata and verification info
|
// Return a positive result, with metadata and verification info
|
||||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||||
|
|
||||||
result.setCachedCryptoInputParcel(cryptoInput);
|
result.setCachedCryptoInputParcel(cryptoInput);
|
||||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||||
result.setDecryptionResult(decryptionResultBuilder.build());
|
result.setDecryptionResult(decryptionResultBuilder.build());
|
||||||
|
result.addSecurityProblems(signatureChecker.getSecurityProblems());
|
||||||
|
result.addSecurityProblems(decryptionResultBuilder.getKeySecurityProblems());
|
||||||
result.setDecryptionMetadata(metadata);
|
result.setDecryptionMetadata(metadata);
|
||||||
result.mOperationTime = opTime;
|
result.mOperationTime = opTime;
|
||||||
|
|
||||||
@@ -659,9 +665,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for insecure encryption key
|
// check for insecure encryption key
|
||||||
if ( ! PgpSecurityConstants.isSecureKey(candidateDecryptionKey)) {
|
KeySecurityProblem keySecurityProblem =
|
||||||
|
PgpSecurityConstants.checkForSecurityProblems(candidateDecryptionKey);
|
||||||
|
if (keySecurityProblem != null) {
|
||||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||||
result.insecureEncryptionKey = true;
|
result.encryptionKeySecurityProblem = keySecurityProblem;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we're good, write down the data for later
|
// we're good, write down the data for later
|
||||||
|
|||||||
@@ -17,6 +17,12 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||||
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||||
@@ -24,9 +30,14 @@ import org.bouncycastle.bcpg.HashAlgorithmTags;
|
|||||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
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.KeySecurityProblem;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.NotWhitelistedCurve;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.SymmetricAlgorithmProblem;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.UnidentifiedKeyProblem;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NIST requirements for 2011-2030 (http://www.keylength.com/en/4/):
|
* NIST requirements for 2011-2030 (http://www.keylength.com/en/4/):
|
||||||
@@ -63,8 +74,11 @@ public class PgpSecurityConstants {
|
|||||||
// CAMELLIA_256: not used widely
|
// CAMELLIA_256: not used widely
|
||||||
));
|
));
|
||||||
|
|
||||||
public static boolean isSecureSymmetricAlgorithm(int id) {
|
public static SymmetricAlgorithmProblem checkSecureSymmetricAlgorithm(int id) {
|
||||||
return sSymmetricAlgorithmsWhitelist.contains(id);
|
if (!sSymmetricAlgorithmsWhitelist.contains(id)) {
|
||||||
|
return new InsecureSymmetricAlgorithm(id);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,8 +107,11 @@ public class PgpSecurityConstants {
|
|||||||
// SHA224: Not used widely, Yahoo argues against it
|
// SHA224: Not used widely, Yahoo argues against it
|
||||||
));
|
));
|
||||||
|
|
||||||
public static boolean isSecureHashAlgorithm(int id) {
|
public static InsecureHashAlgorithm checkSignatureAlgorithmForSecurityProblems(int hashAlgorithm) {
|
||||||
return sHashAlgorithmsWhitelist.contains(id);
|
if (!sHashAlgorithmsWhitelist.contains(hashAlgorithm)) {
|
||||||
|
return new InsecureHashAlgorithm(hashAlgorithm);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,26 +134,51 @@ public class PgpSecurityConstants {
|
|||||||
TeleTrusTNamedCurves.getOID("brainpoolP512r1").getId()
|
TeleTrusTNamedCurves.getOID("brainpoolP512r1").getId()
|
||||||
));
|
));
|
||||||
|
|
||||||
public static boolean isSecureKey(CanonicalizedPublicKey key) {
|
static KeySecurityProblem checkForSecurityProblems(CanonicalizedPublicKey key) {
|
||||||
switch (key.getAlgorithm()) {
|
long masterKeyId = key.getKeyRing().getMasterKeyId();
|
||||||
|
long subKeyId = key.getKeyId();
|
||||||
|
int algorithm = key.getAlgorithm();
|
||||||
|
Integer bitStrength = key.getBitStrength();
|
||||||
|
String curveOid = key.getCurveOid();
|
||||||
|
|
||||||
|
return getKeySecurityProblem(masterKeyId, subKeyId, algorithm, bitStrength, curveOid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static KeySecurityProblem getKeySecurityProblem(long masterKeyId, long subKeyId, int algorithm,
|
||||||
|
Integer bitStrength, String curveOid) {
|
||||||
|
switch (algorithm) {
|
||||||
case PublicKeyAlgorithmTags.RSA_GENERAL: {
|
case PublicKeyAlgorithmTags.RSA_GENERAL: {
|
||||||
return (key.getBitStrength() >= 2048);
|
if (bitStrength < 2048) {
|
||||||
|
return new InsecureBitStrength(masterKeyId, subKeyId, algorithm, bitStrength);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
// RSA_ENCRYPT, RSA_SIGN: deprecated in RFC 4880, use RSA_GENERAL with key flags
|
// RSA_ENCRYPT, RSA_SIGN: deprecated in RFC 4880, use RSA_GENERAL with key flags
|
||||||
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: {
|
case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: {
|
||||||
return (key.getBitStrength() >= 2048);
|
if (bitStrength < 2048) {
|
||||||
|
return new InsecureBitStrength(masterKeyId, subKeyId, algorithm, bitStrength);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
case PublicKeyAlgorithmTags.DSA: {
|
case PublicKeyAlgorithmTags.DSA: {
|
||||||
return (key.getBitStrength() >= 2048);
|
if (bitStrength < 2048) {
|
||||||
|
return new InsecureBitStrength(masterKeyId, subKeyId, algorithm, bitStrength);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
case PublicKeyAlgorithmTags.ECDH:
|
case PublicKeyAlgorithmTags.ECDH:
|
||||||
case PublicKeyAlgorithmTags.ECDSA: {
|
case PublicKeyAlgorithmTags.ECDSA: {
|
||||||
return PgpSecurityConstants.sCurveWhitelist.contains(key.getCurveOid());
|
if (!PgpSecurityConstants.sCurveWhitelist.contains(curveOid)) {
|
||||||
|
return new NotWhitelistedCurve(masterKeyId, subKeyId, curveOid, algorithm);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
// ELGAMAL_GENERAL: deprecated in RFC 4880, use ELGAMAL_ENCRYPT
|
// ELGAMAL_GENERAL: deprecated in RFC 4880, use ELGAMAL_ENCRYPT
|
||||||
// DIFFIE_HELLMAN: unsure
|
// DIFFIE_HELLMAN: unsure
|
||||||
|
// TODO specialize all cases!
|
||||||
default:
|
default:
|
||||||
return false;
|
return new UnidentifiedKeyProblem(masterKeyId, subKeyId, algorithm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,18 +25,21 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||||
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureHashAlgorithm;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -134,10 +137,12 @@ class PgpSignatureChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkKeySecurity(OperationLog log, int indent) {
|
private void checkKeySecurity(OperationLog log, int indent) {
|
||||||
// TODO: checks on signingRing ?
|
// TODO check primary key as well, not only the signing key
|
||||||
if (!PgpSecurityConstants.isSecureKey(signingKey)) {
|
KeySecurityProblem keySecurityProblem =
|
||||||
|
PgpSecurityConstants.checkForSecurityProblems(signingKey);
|
||||||
|
if (keySecurityProblem != null) {
|
||||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||||
signatureResultBuilder.setInsecure(true);
|
signatureResultBuilder.addSecurityProblem(keySecurityProblem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,9 +238,11 @@ class PgpSignatureChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for insecure hash algorithms
|
// check for insecure hash algorithms
|
||||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
|
InsecureHashAlgorithm signatureSecurityProblem =
|
||||||
|
PgpSecurityConstants.checkSignatureAlgorithmForSecurityProblems(signature.getHashAlgorithm());
|
||||||
|
if (signatureSecurityProblem != null) {
|
||||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||||
signatureResultBuilder.setInsecure(true);
|
signatureResultBuilder.addSecurityProblem(signatureSecurityProblem);
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureResultBuilder.setSignatureTimestamp(signature.getCreationTime());
|
signatureResultBuilder.setSignatureTimestamp(signature.getCreationTime());
|
||||||
@@ -268,9 +275,11 @@ class PgpSignatureChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for insecure hash algorithms
|
// check for insecure hash algorithms
|
||||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(onePassSignature.getHashAlgorithm())) {
|
InsecureHashAlgorithm signatureSecurityProblem =
|
||||||
|
PgpSecurityConstants.checkSignatureAlgorithmForSecurityProblems(onePassSignature.getHashAlgorithm());
|
||||||
|
if (signatureSecurityProblem != null) {
|
||||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||||
signatureResultBuilder.setInsecure(true);
|
signatureResultBuilder.addSecurityProblem(signatureSecurityProblem);
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureResultBuilder.setSignatureTimestamp(messageSignature.getCreationTime());
|
signatureResultBuilder.setSignatureTimestamp(messageSignature.getCreationTime());
|
||||||
@@ -288,6 +297,10 @@ class PgpSignatureChecker {
|
|||||||
return signatureResultBuilder.build();
|
return signatureResultBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<SecurityProblem> getSecurityProblems() {
|
||||||
|
return signatureResultBuilder.getSecurityProblems();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
|
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public abstract class SecurityProblem implements Serializable {
|
||||||
|
|
||||||
|
public static abstract class KeySecurityProblem extends SecurityProblem {
|
||||||
|
public final long masterKeyId;
|
||||||
|
public final long subKeyId;
|
||||||
|
public final int algorithm;
|
||||||
|
|
||||||
|
private KeySecurityProblem(long masterKeyId, long subKeyId, int algorithm) {
|
||||||
|
this.masterKeyId = masterKeyId;
|
||||||
|
this.subKeyId = subKeyId;
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class SymmetricAlgorithmProblem extends SecurityProblem {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InsecureBitStrength extends KeySecurityProblem {
|
||||||
|
public final int bitStrength;
|
||||||
|
|
||||||
|
InsecureBitStrength(long masterKeyId, long subKeyId, int algorithm, int bitStrength) {
|
||||||
|
super(masterKeyId, subKeyId, algorithm);
|
||||||
|
this.bitStrength = bitStrength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NotWhitelistedCurve extends KeySecurityProblem {
|
||||||
|
public final String curveOid;
|
||||||
|
|
||||||
|
NotWhitelistedCurve(long masterKeyId, long subKeyId, String curveOid, int algorithm) {
|
||||||
|
super(masterKeyId, subKeyId, algorithm);
|
||||||
|
this.curveOid = curveOid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UnidentifiedKeyProblem extends KeySecurityProblem {
|
||||||
|
UnidentifiedKeyProblem(long masterKeyId, long subKeyId, int algorithm) {
|
||||||
|
super(masterKeyId, subKeyId, algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InsecureHashAlgorithm extends SecurityProblem {
|
||||||
|
public final int hashAlgorithm;
|
||||||
|
|
||||||
|
InsecureHashAlgorithm(int hashAlgorithm) {
|
||||||
|
this.hashAlgorithm = hashAlgorithm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InsecureSymmetricAlgorithm extends SymmetricAlgorithmProblem {
|
||||||
|
public final int symmetricAlgorithm;
|
||||||
|
|
||||||
|
InsecureSymmetricAlgorithm(int symmetricAlgorithm) {
|
||||||
|
this.symmetricAlgorithm = symmetricAlgorithm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MissingMdc extends SymmetricAlgorithmProblem {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -111,6 +111,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid";
|
public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid";
|
||||||
public static final String EXTRA_SECURITY_TOKEN_VERSION = "security_token_version";
|
public static final String EXTRA_SECURITY_TOKEN_VERSION = "security_token_version";
|
||||||
public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints";
|
public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints";
|
||||||
|
private boolean mLinkedTransition;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE})
|
@IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE})
|
||||||
@@ -331,20 +332,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean linkedTransition = getIntent().getBooleanExtra(EXTRA_LINKED_TRANSITION, false);
|
mLinkedTransition = getIntent().getBooleanExtra(EXTRA_LINKED_TRANSITION, false);
|
||||||
if (linkedTransition && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (mLinkedTransition && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
postponeEnterTransition();
|
postponeEnterTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
FragmentManager manager = getSupportFragmentManager();
|
|
||||||
// Create an instance of the fragment
|
|
||||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(mDataUri,
|
|
||||||
linkedTransition ? PostponeType.LINKED : PostponeType.NONE);
|
|
||||||
manager.beginTransaction()
|
|
||||||
.replace(R.id.view_key_fragment, frag)
|
|
||||||
.commit();
|
|
||||||
|
|
||||||
if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) {
|
if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) {
|
||||||
|
FragmentManager manager = getSupportFragmentManager();
|
||||||
final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri);
|
final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri);
|
||||||
manager.beginTransaction()
|
manager.beginTransaction()
|
||||||
.replace(R.id.view_key_keybase_fragment, keybaseFrag)
|
.replace(R.id.view_key_keybase_fragment, keybaseFrag)
|
||||||
@@ -512,7 +506,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
|
|
||||||
private void certifyImmediate() {
|
private void certifyImmediate() {
|
||||||
Intent intent = new Intent(this, CertifyKeyActivity.class);
|
Intent intent = new Intent(this, CertifyKeyActivity.class);
|
||||||
intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId});
|
intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { mMasterKeyId });
|
||||||
|
|
||||||
startActivityForResult(intent, REQUEST_CERTIFY);
|
startActivityForResult(intent, REQUEST_CERTIFY);
|
||||||
}
|
}
|
||||||
@@ -734,6 +728,32 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showMainFragment() {
|
||||||
|
new Handler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
FragmentManager manager = getSupportFragmentManager();
|
||||||
|
|
||||||
|
// unless we must refresh
|
||||||
|
ViewKeyFragment frag = (ViewKeyFragment) manager.findFragmentByTag("view_key_fragment");
|
||||||
|
// if everything is valid, just drop it
|
||||||
|
if (frag != null && frag.isValidForData(mIsSecret)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the main fragment doesn't exist, or is not of the correct type, (re)create it
|
||||||
|
frag = ViewKeyFragment.newInstance(mMasterKeyId, mIsSecret,
|
||||||
|
mLinkedTransition ? PostponeType.LINKED : PostponeType.NONE);
|
||||||
|
// get rid of possible backstack, this fragment is always at the bottom
|
||||||
|
manager.popBackStack("security_token", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||||
|
manager.beginTransaction()
|
||||||
|
.replace(R.id.view_key_fragment, frag, "view_key_fragment")
|
||||||
|
// if this gets lost, it doesn't really matter since the loader will reinstate it onResume
|
||||||
|
.commitAllowingStateLoss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void encrypt(Uri dataUri, boolean text) {
|
private void encrypt(Uri dataUri, boolean text) {
|
||||||
// If there is no encryption key, don't bother.
|
// If there is no encryption key, don't bother.
|
||||||
if (!mHasEncrypt) {
|
if (!mHasEncrypt) {
|
||||||
@@ -900,6 +920,15 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||||
mFingerprint = data.getBlob(INDEX_FINGERPRINT);
|
mFingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||||
mFingerprintString = KeyFormattingUtils.convertFingerprintToHex(mFingerprint);
|
mFingerprintString = KeyFormattingUtils.convertFingerprintToHex(mFingerprint);
|
||||||
|
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||||
|
mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
|
||||||
|
mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
|
||||||
|
mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
|
||||||
|
mIsSecure = data.getInt(INDEX_IS_SECURE) == 1;
|
||||||
|
mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
|
||||||
|
|
||||||
|
// queue showing of the main fragment
|
||||||
|
showMainFragment();
|
||||||
|
|
||||||
// if it wasn't shown yet, display token fragment
|
// if it wasn't shown yet, display token fragment
|
||||||
if (mShowSecurityTokenAfterCreation && getIntent().hasExtra(EXTRA_SECURITY_TOKEN_AID)) {
|
if (mShowSecurityTokenAfterCreation && getIntent().hasExtra(EXTRA_SECURITY_TOKEN_AID)) {
|
||||||
@@ -912,13 +941,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid, tokenVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
|
||||||
mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
|
|
||||||
mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
|
|
||||||
mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
|
|
||||||
mIsSecure = data.getInt(INDEX_IS_SECURE) == 1;
|
|
||||||
mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
|
|
||||||
|
|
||||||
// if the refresh animation isn't playing
|
// if the refresh animation isn't playing
|
||||||
if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
|
if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
|
||||||
// re-create options menu based on mIsSecret, mIsVerified
|
// re-create options menu based on mIsSecret, mIsVerified
|
||||||
@@ -944,10 +966,24 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
boolean showStatusText = mIsSecure && !mIsExpired && !mIsRevoked;
|
||||||
|
if (showStatusText) {
|
||||||
|
mStatusText.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (mIsSecret) {
|
||||||
|
mStatusText.setText(R.string.view_key_my_key);
|
||||||
|
} else if (mIsVerified) {
|
||||||
|
mStatusText.setText(R.string.view_key_verified);
|
||||||
|
} else {
|
||||||
|
mStatusText.setText(R.string.view_key_unverified);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mStatusText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
// Note: order is important
|
// Note: order is important
|
||||||
int color;
|
int color;
|
||||||
if (mIsRevoked) {
|
if (mIsRevoked) {
|
||||||
mStatusText.setText(R.string.view_key_revoked);
|
|
||||||
mStatusImage.setVisibility(View.VISIBLE);
|
mStatusImage.setVisibility(View.VISIBLE);
|
||||||
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
|
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
|
||||||
State.REVOKED, R.color.icons, true);
|
State.REVOKED, R.color.icons, true);
|
||||||
@@ -960,7 +996,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
hideFab();
|
hideFab();
|
||||||
mQrCodeLayout.setVisibility(View.GONE);
|
mQrCodeLayout.setVisibility(View.GONE);
|
||||||
} else if (!mIsSecure) {
|
} else if (!mIsSecure) {
|
||||||
mStatusText.setText(R.string.view_key_insecure);
|
|
||||||
mStatusImage.setVisibility(View.VISIBLE);
|
mStatusImage.setVisibility(View.VISIBLE);
|
||||||
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
|
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
|
||||||
State.INSECURE, R.color.icons, true);
|
State.INSECURE, R.color.icons, true);
|
||||||
@@ -973,11 +1008,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
hideFab();
|
hideFab();
|
||||||
mQrCodeLayout.setVisibility(View.GONE);
|
mQrCodeLayout.setVisibility(View.GONE);
|
||||||
} else if (mIsExpired) {
|
} else if (mIsExpired) {
|
||||||
if (mIsSecret) {
|
|
||||||
mStatusText.setText(R.string.view_key_expired_secret);
|
|
||||||
} else {
|
|
||||||
mStatusText.setText(R.string.view_key_expired);
|
|
||||||
}
|
|
||||||
mStatusImage.setVisibility(View.VISIBLE);
|
mStatusImage.setVisibility(View.VISIBLE);
|
||||||
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
|
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
|
||||||
State.EXPIRED, R.color.icons, true);
|
State.EXPIRED, R.color.icons, true);
|
||||||
@@ -990,7 +1020,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||||||
hideFab();
|
hideFab();
|
||||||
mQrCodeLayout.setVisibility(View.GONE);
|
mQrCodeLayout.setVisibility(View.GONE);
|
||||||
} else if (mIsSecret) {
|
} else if (mIsSecret) {
|
||||||
mStatusText.setText(R.string.view_key_my_key);
|
|
||||||
mStatusImage.setVisibility(View.GONE);
|
mStatusImage.setVisibility(View.GONE);
|
||||||
// noinspection deprecation, fix requires api level 23
|
// noinspection deprecation, fix requires api level 23
|
||||||
color = getResources().getColor(R.color.key_flag_green);
|
color = getResources().getColor(R.color.key_flag_green);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.base.LoaderFragment;
|
import org.sufficientlysecure.keychain.ui.base.LoaderFragment;
|
||||||
@@ -66,14 +68,16 @@ import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
|
|||||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
|
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener;
|
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener;
|
||||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyHealthPresenter;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyHealthCardView;
|
||||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
|
||||||
|
|
||||||
public class ViewKeyFragment extends LoaderFragment implements
|
public class ViewKeyFragment extends LoaderFragment implements
|
||||||
LoaderManager.LoaderCallbacks<Cursor> {
|
LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
public static final String ARG_DATA_URI = "uri";
|
public static final String ARG_MASTER_KEY_ID = "master_key_id";
|
||||||
|
public static final String ARG_IS_SECRET = "is_secret";
|
||||||
public static final String ARG_POSTPONE_TYPE = "postpone_type";
|
public static final String ARG_POSTPONE_TYPE = "postpone_type";
|
||||||
|
|
||||||
private ListView mUserIds;
|
private ListView mUserIds;
|
||||||
@@ -84,20 +88,16 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
boolean mIsSecret = false;
|
boolean mIsSecret = false;
|
||||||
|
|
||||||
private static final int LOADER_ID_UNIFIED = 0;
|
|
||||||
private static final int LOADER_ID_USER_IDS = 1;
|
private static final int LOADER_ID_USER_IDS = 1;
|
||||||
private static final int LOADER_ID_LINKED_IDS = 2;
|
private static final int LOADER_ID_LINKED_IDS = 2;
|
||||||
private static final int LOADER_ID_LINKED_CONTACT = 3;
|
private static final int LOADER_ID_LINKED_CONTACT = 3;
|
||||||
|
private static final int LOADER_ID_SUBKEY_STATUS = 4;
|
||||||
private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID
|
|
||||||
= "loader_linked_contact_master_key_id";
|
|
||||||
private static final String LOADER_EXTRA_LINKED_CONTACT_IS_SECRET
|
|
||||||
= "loader_linked_contact_is_secret";
|
|
||||||
|
|
||||||
private UserIdsAdapter mUserIdsAdapter;
|
private UserIdsAdapter mUserIdsAdapter;
|
||||||
private LinkedIdsAdapter mLinkedIdsAdapter;
|
private LinkedIdsAdapter mLinkedIdsAdapter;
|
||||||
|
|
||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
|
||||||
private PostponeType mPostponeType;
|
private PostponeType mPostponeType;
|
||||||
|
|
||||||
private CardView mSystemContactCard;
|
private CardView mSystemContactCard;
|
||||||
@@ -111,13 +111,19 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
private byte[] mFingerprint;
|
private byte[] mFingerprint;
|
||||||
private TextView mLinkedIdsExpander;
|
private TextView mLinkedIdsExpander;
|
||||||
|
|
||||||
|
KeyHealthCardView mKeyHealthCard;
|
||||||
|
KeyHealthPresenter mKeyHealthPresenter;
|
||||||
|
|
||||||
|
private long mMasterKeyId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
*/
|
*/
|
||||||
public static ViewKeyFragment newInstance(Uri dataUri, PostponeType postponeType) {
|
public static ViewKeyFragment newInstance(long masterKeyId, boolean isSecret, PostponeType postponeType) {
|
||||||
ViewKeyFragment frag = new ViewKeyFragment();
|
ViewKeyFragment frag = new ViewKeyFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
args.putLong(ARG_MASTER_KEY_ID, masterKeyId);
|
||||||
|
args.putBoolean(ARG_IS_SECRET, isSecret);
|
||||||
args.putString(ARG_POSTPONE_TYPE, postponeType.toString());
|
args.putString(ARG_POSTPONE_TYPE, postponeType.toString());
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
@@ -169,6 +175,8 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mKeyHealthCard = (KeyHealthCardView) view.findViewById(R.id.subkey_status_card);
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +231,29 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
mMasterKeyId = getArguments().getLong(ARG_MASTER_KEY_ID);
|
||||||
|
mDataUri = KeyRings.buildGenericKeyRingUri(mMasterKeyId);
|
||||||
|
mIsSecret = getArguments().getBoolean(ARG_IS_SECRET);
|
||||||
|
mPostponeType = PostponeType.valueOf(getArguments().getString(ARG_POSTPONE_TYPE));
|
||||||
|
|
||||||
|
// load user ids after we know if it's a secret key
|
||||||
|
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
||||||
|
mUserIds.setAdapter(mUserIdsAdapter);
|
||||||
|
|
||||||
|
// initialize loaders, which will take care of auto-refresh on change
|
||||||
|
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||||
|
initLinkedContactLoader();
|
||||||
|
initCardButtonsVisibility(mIsSecret);
|
||||||
|
|
||||||
|
mKeyHealthPresenter = new KeyHealthPresenter(
|
||||||
|
getContext(), mKeyHealthCard, LOADER_ID_SUBKEY_STATUS, mMasterKeyId, mIsSecret);
|
||||||
|
mKeyHealthPresenter.startLoader(getLoaderManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showUserIdInfo(final int position) {
|
private void showUserIdInfo(final int position) {
|
||||||
@@ -249,7 +279,9 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
*/
|
*/
|
||||||
private void loadLinkedSystemContact(final long contactId) {
|
private void loadLinkedSystemContact(final long contactId) {
|
||||||
// contact doesn't exist, stop
|
// contact doesn't exist, stop
|
||||||
if (contactId == -1) return;
|
if (contactId == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Context context = mSystemContactName.getContext();
|
final Context context = mSystemContactName.getContext();
|
||||||
ContactHelper contactHelper = new ContactHelper(context);
|
ContactHelper contactHelper = new ContactHelper(context);
|
||||||
@@ -265,7 +297,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
contactName = contactHelper.getContactName(contactId);
|
contactName = contactHelper.getContactName(contactId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contactName != null) {//contact name exists for given master key
|
if (contactName != null) { //contact name exists for given master key
|
||||||
showLinkedSystemContact();
|
showLinkedSystemContact();
|
||||||
|
|
||||||
mSystemContactName.setText(contactName);
|
mSystemContactName.setText(contactName);
|
||||||
@@ -311,21 +343,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
|
|
||||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
|
||||||
mPostponeType = PostponeType.valueOf(getArguments().getString(ARG_POSTPONE_TYPE));
|
|
||||||
if (dataUri == null) {
|
|
||||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
|
||||||
getActivity().finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadData(dataUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
// if a result has been returned, display a notify
|
// if a result has been returned, display a notify
|
||||||
@@ -337,58 +354,16 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final String[] UNIFIED_PROJECTION = new String[]{
|
|
||||||
KeychainContract.KeyRings._ID,
|
|
||||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
|
||||||
KeychainContract.KeyRings.USER_ID,
|
|
||||||
KeychainContract.KeyRings.IS_REVOKED,
|
|
||||||
KeychainContract.KeyRings.IS_EXPIRED,
|
|
||||||
KeychainContract.KeyRings.VERIFIED,
|
|
||||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
|
||||||
KeychainContract.KeyRings.FINGERPRINT,
|
|
||||||
KeychainContract.KeyRings.HAS_ENCRYPT
|
|
||||||
};
|
|
||||||
|
|
||||||
static final int INDEX_MASTER_KEY_ID = 1;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
static final int INDEX_USER_ID = 2;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
static final int INDEX_IS_REVOKED = 3;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
static final int INDEX_IS_EXPIRED = 4;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
static final int INDEX_VERIFIED = 5;
|
|
||||||
static final int INDEX_HAS_ANY_SECRET = 6;
|
|
||||||
static final int INDEX_FINGERPRINT = 7;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
static final int INDEX_HAS_ENCRYPT = 8;
|
|
||||||
|
|
||||||
private static final String[] RAW_CONTACT_PROJECTION = {
|
private static final String[] RAW_CONTACT_PROJECTION = {
|
||||||
ContactsContract.RawContacts.CONTACT_ID
|
ContactsContract.RawContacts.CONTACT_ID
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final int INDEX_CONTACT_ID = 0;
|
private static final int INDEX_CONTACT_ID = 0;
|
||||||
|
|
||||||
private void loadData(Uri dataUri) {
|
|
||||||
mDataUri = dataUri;
|
|
||||||
|
|
||||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri);
|
|
||||||
|
|
||||||
// Prepare the loaders. Either re-connect with an existing ones,
|
|
||||||
// or start new ones.
|
|
||||||
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case LOADER_ID_UNIFIED: {
|
|
||||||
setContentShown(false, false);
|
|
||||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
|
||||||
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
case LOADER_ID_USER_IDS: {
|
case LOADER_ID_USER_IDS: {
|
||||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||||
}
|
}
|
||||||
@@ -401,11 +376,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
// we need a separate loader for linked contact
|
// we need a separate loader for linked contact
|
||||||
// to ensure refreshing on verification
|
// to ensure refreshing on verification
|
||||||
|
|
||||||
// passed in args to explicitly specify their need
|
Uri baseUri = mIsSecret ? ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI :
|
||||||
long masterKeyId = args.getLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID);
|
|
||||||
boolean isSecret = args.getBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET);
|
|
||||||
|
|
||||||
Uri baseUri = isSecret ? ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI :
|
|
||||||
ContactsContract.RawContacts.CONTENT_URI;
|
ContactsContract.RawContacts.CONTENT_URI;
|
||||||
|
|
||||||
return new CursorLoader(
|
return new CursorLoader(
|
||||||
@@ -417,12 +388,16 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
ContactsContract.RawContacts.DELETED + "=?",
|
ContactsContract.RawContacts.DELETED + "=?",
|
||||||
new String[]{
|
new String[]{
|
||||||
Constants.ACCOUNT_TYPE,
|
Constants.ACCOUNT_TYPE,
|
||||||
Long.toString(masterKeyId),
|
Long.toString(mMasterKeyId),
|
||||||
"0" // "0" for "not deleted"
|
"0" // "0" for "not deleted"
|
||||||
},
|
},
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case LOADER_ID_SUBKEY_STATUS: {
|
||||||
|
throw new IllegalStateException("This callback should never end up here!");
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -439,22 +414,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
// Swap the new cursor in. (The framework will take care of closing the
|
// Swap the new cursor in. (The framework will take care of closing the
|
||||||
// old cursor once we return.)
|
// old cursor once we return.)
|
||||||
switch (loader.getId()) {
|
switch (loader.getId()) {
|
||||||
case LOADER_ID_UNIFIED: {
|
|
||||||
if (data.getCount() == 1 && data.moveToFirst()) {
|
|
||||||
|
|
||||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
|
||||||
mFingerprint = data.getBlob(INDEX_FINGERPRINT);
|
|
||||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
|
||||||
|
|
||||||
// init other things after we know if it's a secret key
|
|
||||||
initUserIds(mIsSecret);
|
|
||||||
initLinkedIds(mIsSecret);
|
|
||||||
initLinkedContactLoader(masterKeyId, mIsSecret);
|
|
||||||
initCardButtonsVisibility(mIsSecret);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case LOADER_ID_USER_IDS: {
|
case LOADER_ID_USER_IDS: {
|
||||||
setContentShown(true, false);
|
setContentShown(true, false);
|
||||||
mUserIdsAdapter.swapCursor(data);
|
mUserIdsAdapter.swapCursor(data);
|
||||||
@@ -494,25 +453,14 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case LOADER_ID_SUBKEY_STATUS: {
|
||||||
|
throw new IllegalStateException("This callback should never end up here!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initUserIds(boolean isSecret) {
|
private void initLinkedContactLoader() {
|
||||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !isSecret, null);
|
|
||||||
mUserIds.setAdapter(mUserIdsAdapter);
|
|
||||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initLinkedIds(boolean isSecret) {
|
|
||||||
if (Preferences.getPreferences(getActivity()).getExperimentalEnableLinkedIdentities()) {
|
|
||||||
mLinkedIdsAdapter =
|
|
||||||
new LinkedIdsAdapter(getActivity(), null, 0, isSecret, mLinkedIdsExpander);
|
|
||||||
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
|
||||||
getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initLinkedContactLoader(long masterKeyId, boolean isSecret) {
|
|
||||||
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_CONTACTS)
|
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_CONTACTS)
|
||||||
== PackageManager.PERMISSION_DENIED) {
|
== PackageManager.PERMISSION_DENIED) {
|
||||||
Log.w(Constants.TAG, "loading linked system contact not possible READ_CONTACTS permission denied!");
|
Log.w(Constants.TAG, "loading linked system contact not possible READ_CONTACTS permission denied!");
|
||||||
@@ -521,8 +469,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bundle linkedContactData = new Bundle();
|
Bundle linkedContactData = new Bundle();
|
||||||
linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId);
|
|
||||||
linkedContactData.putBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET, isSecret);
|
|
||||||
|
|
||||||
// initialises loader for contact query so we can listen to any updates
|
// initialises loader for contact query so we can listen to any updates
|
||||||
getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this);
|
getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this);
|
||||||
@@ -557,7 +503,14 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
mLinkedIdsAdapter.swapCursor(null);
|
mLinkedIdsAdapter.swapCursor(null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case LOADER_ID_SUBKEY_STATUS:
|
||||||
|
mKeyHealthPresenter.onLoaderReset(loader);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isValidForData(boolean isSecret) {
|
||||||
|
return isSecret == mIsSecret;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ public class KeyFormattingUtils {
|
|||||||
return getAlgorithmInfo(null, algorithm, keySize, oid);
|
return getAlgorithmInfo(null, algorithm, keySize, oid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getAlgorithmInfo(int algorithm) {
|
||||||
|
return getAlgorithmInfo(null, algorithm, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
|
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.ColorRes;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.NotWhitelistedCurve;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.UnidentifiedKeyProblem;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyHealthPresenter.KeyHealthClickListener;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyHealthPresenter.KeyHealthMvpView;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyHealthPresenter.KeyHealthStatus;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyStatusList.KeyDisplayStatus;
|
||||||
|
|
||||||
|
|
||||||
|
public class KeyHealthCardView extends CardView implements KeyHealthMvpView, OnClickListener {
|
||||||
|
private final View vLayout;
|
||||||
|
private final TextView vTitle, vSubtitle;
|
||||||
|
private final ImageView vIcon;
|
||||||
|
private final ImageView vExpander;
|
||||||
|
private final KeyStatusList vKeyStatusList;
|
||||||
|
private final View vKeyStatusDivider;
|
||||||
|
private final View vInsecureLayout;
|
||||||
|
private final TextView vInsecureProblem;
|
||||||
|
private final TextView vInsecureSolution;
|
||||||
|
private final View vExpiryLayout;
|
||||||
|
private final TextView vExpiryText;
|
||||||
|
|
||||||
|
private KeyHealthClickListener keyHealthClickListener;
|
||||||
|
|
||||||
|
public KeyHealthCardView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
View view = LayoutInflater.from(context).inflate(R.layout.key_health_card_content, this, true);
|
||||||
|
|
||||||
|
vLayout = view.findViewById(R.id.key_health_layout);
|
||||||
|
vTitle = (TextView) view.findViewById(R.id.key_health_title);
|
||||||
|
vSubtitle = (TextView) view.findViewById(R.id.key_health_subtitle);
|
||||||
|
vIcon = (ImageView) view.findViewById(R.id.key_health_icon);
|
||||||
|
vExpander = (ImageView) view.findViewById(R.id.key_health_expander);
|
||||||
|
|
||||||
|
vLayout.setOnClickListener(this);
|
||||||
|
|
||||||
|
vKeyStatusDivider = view.findViewById(R.id.key_health_divider);
|
||||||
|
vKeyStatusList = (KeyStatusList) view.findViewById(R.id.key_health_status_list);
|
||||||
|
|
||||||
|
vInsecureLayout = view.findViewById(R.id.key_insecure_layout);
|
||||||
|
vInsecureProblem = (TextView) view.findViewById(R.id.key_insecure_problem);
|
||||||
|
vInsecureSolution = (TextView) view.findViewById(R.id.key_insecure_solution);
|
||||||
|
|
||||||
|
vExpiryLayout = view.findViewById(R.id.key_expiry_layout);
|
||||||
|
vExpiryText = (TextView) view.findViewById(R.id.key_expiry_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum KeyHealthDisplayStatus {
|
||||||
|
OK (R.string.key_health_ok_title, R.string.key_health_ok_subtitle,
|
||||||
|
R.drawable.ic_check_black_24dp, R.color.android_green_light),
|
||||||
|
DIVERT (R.string.key_health_divert_title, R.string.key_health_divert_subtitle,
|
||||||
|
R.drawable.yubi_icon_24dp, R.color.md_black_1000),
|
||||||
|
REVOKED (R.string.key_health_revoked_title, R.string.key_health_revoked_subtitle,
|
||||||
|
R.drawable.ic_close_black_24dp, R.color.android_red_light),
|
||||||
|
EXPIRED (R.string.key_health_expired_title, R.string.key_health_expired_subtitle,
|
||||||
|
R.drawable.status_signature_expired_cutout_24dp, R.color.android_red_light),
|
||||||
|
INSECURE (R.string.key_health_insecure_title, R.string.key_health_insecure_subtitle,
|
||||||
|
R.drawable.ic_close_black_24dp, R.color.android_red_light),
|
||||||
|
BROKEN(R.string.key_health_broken_title, R.string.key_health_broken_subtitle,
|
||||||
|
R.drawable.broken_heart_24dp, R.color.android_red_light),
|
||||||
|
SIGN_ONLY (R.string.key_health_sign_only_title, R.string.key_health_sign_only_subtitle,
|
||||||
|
R.drawable.ic_check_black_24dp, R.color.android_green_light),
|
||||||
|
STRIPPED (R.string.key_health_stripped_title, R.string.key_health_stripped_subtitle,
|
||||||
|
R.drawable.ic_check_black_24dp, R.color.android_green_light),
|
||||||
|
PARTIAL_STRIPPED (R.string.key_health_partial_stripped_title, R.string.key_health_partial_stripped_subtitle,
|
||||||
|
R.drawable.ic_check_black_24dp, R.color.android_green_light);
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private final int title, subtitle;
|
||||||
|
@DrawableRes
|
||||||
|
private final int icon;
|
||||||
|
@ColorRes
|
||||||
|
private final int iconColor;
|
||||||
|
|
||||||
|
KeyHealthDisplayStatus(@StringRes int title, @StringRes int subtitle,
|
||||||
|
@DrawableRes int icon, @ColorRes int iconColor) {
|
||||||
|
this.title = title;
|
||||||
|
this.subtitle = subtitle;
|
||||||
|
this.icon = icon;
|
||||||
|
this.iconColor = iconColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKeyStatus(KeyHealthStatus keyHealthStatus) {
|
||||||
|
switch (keyHealthStatus) {
|
||||||
|
case OK:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.OK);
|
||||||
|
break;
|
||||||
|
case DIVERT:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.DIVERT);
|
||||||
|
break;
|
||||||
|
case REVOKED:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.REVOKED);
|
||||||
|
break;
|
||||||
|
case EXPIRED:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.EXPIRED);
|
||||||
|
break;
|
||||||
|
case INSECURE:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.INSECURE);
|
||||||
|
break;
|
||||||
|
case BROKEN:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.BROKEN);
|
||||||
|
break;
|
||||||
|
case STRIPPED:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.STRIPPED);
|
||||||
|
break;
|
||||||
|
case SIGN_ONLY:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.SIGN_ONLY);
|
||||||
|
break;
|
||||||
|
case PARTIAL_STRIPPED:
|
||||||
|
setKeyStatus(KeyHealthDisplayStatus.PARTIAL_STRIPPED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrimarySecurityProblem(KeySecurityProblem securityProblem) {
|
||||||
|
if (securityProblem == null) {
|
||||||
|
vInsecureLayout.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vInsecureLayout.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (securityProblem instanceof InsecureBitStrength) {
|
||||||
|
InsecureBitStrength insecureBitStrength = (InsecureBitStrength) securityProblem;
|
||||||
|
vInsecureProblem.setText(getResources().getString(R.string.key_insecure_bitstrength_2048_problem,
|
||||||
|
KeyFormattingUtils.getAlgorithmInfo(insecureBitStrength.algorithm),
|
||||||
|
Integer.toString(insecureBitStrength.bitStrength)));
|
||||||
|
vInsecureSolution.setText(R.string.key_insecure_bitstrength_2048_solution);
|
||||||
|
} else if (securityProblem instanceof NotWhitelistedCurve) {
|
||||||
|
NotWhitelistedCurve notWhitelistedCurve = (NotWhitelistedCurve) securityProblem;
|
||||||
|
|
||||||
|
String curveName = KeyFormattingUtils.getCurveInfo(getContext(), notWhitelistedCurve.curveOid);
|
||||||
|
vInsecureProblem.setText(getResources().getString(R.string.key_insecure_unknown_curve_problem, curveName));
|
||||||
|
vInsecureSolution.setText(R.string.key_insecure_unknown_curve_solution);
|
||||||
|
} else if (securityProblem instanceof UnidentifiedKeyProblem) {
|
||||||
|
vInsecureProblem.setText(R.string.key_insecure_unidentified_problem);
|
||||||
|
vInsecureSolution.setText(R.string.key_insecure_unknown_curve_solution);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("all subclasses of KeySecurityProblem must be handled!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrimaryExpiryDate(Date expiry) {
|
||||||
|
if (expiry == null) {
|
||||||
|
vExpiryLayout.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vExpiryLayout.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
String expiryText = DateFormat.getMediumDateFormat(getContext()).format(expiry);
|
||||||
|
vExpiryText.setText(getResources().getString(R.string.key_expiry_text, expiryText));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if (keyHealthClickListener != null) {
|
||||||
|
keyHealthClickListener.onKeyHealthClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnHealthClickListener(KeyHealthClickListener keyHealthClickListener) {
|
||||||
|
this.keyHealthClickListener = keyHealthClickListener;
|
||||||
|
vLayout.setClickable(keyHealthClickListener != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShowExpander(boolean showExpander) {
|
||||||
|
vLayout.setClickable(showExpander);
|
||||||
|
vExpander.setVisibility(showExpander ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showExpandedState(KeyDisplayStatus certifyStatus, KeyDisplayStatus signStatus,
|
||||||
|
KeyDisplayStatus encryptStatus) {
|
||||||
|
if (certifyStatus == null && signStatus == null && encryptStatus == null) {
|
||||||
|
vKeyStatusList.setVisibility(View.GONE);
|
||||||
|
vKeyStatusDivider.setVisibility(View.GONE);
|
||||||
|
vExpander.setImageResource(R.drawable.ic_expand_more_black_24dp);
|
||||||
|
} else {
|
||||||
|
vKeyStatusList.setVisibility(View.VISIBLE);
|
||||||
|
vKeyStatusDivider.setVisibility(View.VISIBLE);
|
||||||
|
vExpander.setImageResource(R.drawable.ic_expand_less_black_24dp);
|
||||||
|
|
||||||
|
vKeyStatusList.setCertifyStatus(certifyStatus);
|
||||||
|
vKeyStatusList.setSignStatus(signStatus);
|
||||||
|
vKeyStatusList.setDecryptStatus(encryptStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideExpandedInfo() {
|
||||||
|
showExpandedState(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setKeyStatus(KeyHealthDisplayStatus keyHealthDisplayStatus) {
|
||||||
|
vTitle.setText(keyHealthDisplayStatus.title);
|
||||||
|
vSubtitle.setText(keyHealthDisplayStatus.subtitle);
|
||||||
|
vIcon.setImageResource(keyHealthDisplayStatus.icon);
|
||||||
|
vIcon.setColorFilter(ContextCompat.getColor(getContext(), keyHealthDisplayStatus.iconColor));
|
||||||
|
|
||||||
|
setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyStatusList.KeyDisplayStatus;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.SubkeyStatusLoader.KeySubkeyStatus;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.SubkeyStatusLoader.SubKeyItem;
|
||||||
|
|
||||||
|
|
||||||
|
public class KeyHealthPresenter implements LoaderCallbacks<KeySubkeyStatus> {
|
||||||
|
static final Comparator<SubKeyItem> SUBKEY_COMPARATOR = new Comparator<SubKeyItem>() {
|
||||||
|
@Override
|
||||||
|
public int compare(SubKeyItem one, SubKeyItem two) {
|
||||||
|
// if one is valid and the other isn't, the valid one always comes first
|
||||||
|
if (one.isValid() ^ two.isValid()) {
|
||||||
|
return one.isValid() ? -1 : 1;
|
||||||
|
}
|
||||||
|
// compare usability, if one is "more usable" than the other, that one comes first
|
||||||
|
int usability = one.mSecretKeyType.compareUsability(two.mSecretKeyType);
|
||||||
|
if (usability != 0) {
|
||||||
|
return usability;
|
||||||
|
}
|
||||||
|
if ((one.mSecurityProblem == null) ^ (two.mSecurityProblem == null)) {
|
||||||
|
return one.mSecurityProblem == null ? -1 : 1;
|
||||||
|
}
|
||||||
|
// otherwise, the newer one comes first
|
||||||
|
return one.newerThan(two) ? -1 : 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final KeyHealthMvpView view;
|
||||||
|
private final int loaderId;
|
||||||
|
|
||||||
|
private final long masterKeyId;
|
||||||
|
private final boolean isSecret;
|
||||||
|
|
||||||
|
private KeySubkeyStatus subkeyStatus;
|
||||||
|
private boolean showingExpandedInfo;
|
||||||
|
|
||||||
|
|
||||||
|
public KeyHealthPresenter(Context context, KeyHealthMvpView view, int loaderId, long masterKeyId, boolean isSecret) {
|
||||||
|
this.context = context;
|
||||||
|
this.view = view;
|
||||||
|
this.loaderId = loaderId;
|
||||||
|
|
||||||
|
this.masterKeyId = masterKeyId;
|
||||||
|
this.isSecret = isSecret;
|
||||||
|
|
||||||
|
view.setOnHealthClickListener(new KeyHealthClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onKeyHealthClick() {
|
||||||
|
KeyHealthPresenter.this.onKeyHealthClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startLoader(LoaderManager loaderManager) {
|
||||||
|
loaderManager.restartLoader(loaderId, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<KeySubkeyStatus> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new SubkeyStatusLoader(context, context.getContentResolver(), masterKeyId, SUBKEY_COMPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<KeySubkeyStatus> loader, KeySubkeyStatus subkeyStatus) {
|
||||||
|
this.subkeyStatus = subkeyStatus;
|
||||||
|
|
||||||
|
KeyHealthStatus keyHealthStatus = determineKeyHealthStatus(subkeyStatus);
|
||||||
|
|
||||||
|
boolean isInsecure = keyHealthStatus == KeyHealthStatus.INSECURE;
|
||||||
|
boolean isExpired = keyHealthStatus == KeyHealthStatus.EXPIRED;
|
||||||
|
if (isInsecure) {
|
||||||
|
boolean primaryKeySecurityProblem = subkeyStatus.keyCertify.mSecurityProblem != null;
|
||||||
|
if (primaryKeySecurityProblem) {
|
||||||
|
view.setKeyStatus(keyHealthStatus);
|
||||||
|
view.setPrimarySecurityProblem(subkeyStatus.keyCertify.mSecurityProblem);
|
||||||
|
view.setShowExpander(false);
|
||||||
|
} else {
|
||||||
|
view.setKeyStatus(keyHealthStatus);
|
||||||
|
view.setShowExpander(false);
|
||||||
|
displayExpandedInfo(false);
|
||||||
|
}
|
||||||
|
} else if (isExpired) {
|
||||||
|
view.setKeyStatus(keyHealthStatus);
|
||||||
|
view.setPrimaryExpiryDate(subkeyStatus.keyCertify.mExpiry);
|
||||||
|
view.setShowExpander(false);
|
||||||
|
} else {
|
||||||
|
view.setKeyStatus(keyHealthStatus);
|
||||||
|
view.setShowExpander(keyHealthStatus != KeyHealthStatus.REVOKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyHealthStatus determineKeyHealthStatus(KeySubkeyStatus subkeyStatus) {
|
||||||
|
SubKeyItem keyCertify = subkeyStatus.keyCertify;
|
||||||
|
if (keyCertify.mIsRevoked) {
|
||||||
|
return KeyHealthStatus.REVOKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCertify.mIsExpired) {
|
||||||
|
return KeyHealthStatus.EXPIRED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCertify.mSecurityProblem != null) {
|
||||||
|
return KeyHealthStatus.INSECURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subkeyStatus.keysSign.isEmpty() && subkeyStatus.keysEncrypt.isEmpty()) {
|
||||||
|
SubKeyItem keySign = subkeyStatus.keysSign.get(0);
|
||||||
|
if (!keySign.isValid()) {
|
||||||
|
return KeyHealthStatus.BROKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keySign.mSecurityProblem != null) {
|
||||||
|
return KeyHealthStatus.INSECURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyHealthStatus.SIGN_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subkeyStatus.keysSign.isEmpty() || subkeyStatus.keysEncrypt.isEmpty()) {
|
||||||
|
return KeyHealthStatus.BROKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubKeyItem keySign = subkeyStatus.keysSign.get(0);
|
||||||
|
SubKeyItem keyEncrypt = subkeyStatus.keysEncrypt.get(0);
|
||||||
|
|
||||||
|
if (keySign.mSecurityProblem != null && keySign.isValid()
|
||||||
|
|| keyEncrypt.mSecurityProblem != null && keyEncrypt.isValid()) {
|
||||||
|
return KeyHealthStatus.INSECURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keySign.isValid() || !keyEncrypt.isValid()) {
|
||||||
|
return KeyHealthStatus.BROKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCertify.mSecretKeyType == SecretKeyType.GNU_DUMMY
|
||||||
|
&& keySign.mSecretKeyType == SecretKeyType.GNU_DUMMY
|
||||||
|
&& keyEncrypt.mSecretKeyType == SecretKeyType.GNU_DUMMY) {
|
||||||
|
return KeyHealthStatus.STRIPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCertify.mSecretKeyType == SecretKeyType.GNU_DUMMY
|
||||||
|
|| keySign.mSecretKeyType == SecretKeyType.GNU_DUMMY
|
||||||
|
|| keyEncrypt.mSecretKeyType == SecretKeyType.GNU_DUMMY) {
|
||||||
|
return KeyHealthStatus.PARTIAL_STRIPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCertify.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD
|
||||||
|
&& keySign.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD
|
||||||
|
&& keyEncrypt.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD) {
|
||||||
|
return KeyHealthStatus.DIVERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyHealthStatus.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader loader) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onKeyHealthClick() {
|
||||||
|
if (showingExpandedInfo) {
|
||||||
|
showingExpandedInfo = false;
|
||||||
|
view.hideExpandedInfo();
|
||||||
|
} else {
|
||||||
|
showingExpandedInfo = true;
|
||||||
|
displayExpandedInfo(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayExpandedInfo(boolean displayAll) {
|
||||||
|
SubKeyItem keyCertify = subkeyStatus.keyCertify;
|
||||||
|
SubKeyItem keySign = subkeyStatus.keysSign.isEmpty() ? null : subkeyStatus.keysSign.get(0);
|
||||||
|
SubKeyItem keyEncrypt = subkeyStatus.keysEncrypt.isEmpty() ? null : subkeyStatus.keysEncrypt.get(0);
|
||||||
|
|
||||||
|
KeyDisplayStatus certDisplayStatus = getKeyDisplayStatus(keyCertify);
|
||||||
|
KeyDisplayStatus signDisplayStatus = getKeyDisplayStatus(keySign);
|
||||||
|
KeyDisplayStatus encryptDisplayStatus = getKeyDisplayStatus(keyEncrypt);
|
||||||
|
|
||||||
|
if (!displayAll) {
|
||||||
|
if (certDisplayStatus == KeyDisplayStatus.OK) {
|
||||||
|
certDisplayStatus = null;
|
||||||
|
}
|
||||||
|
if (certDisplayStatus == KeyDisplayStatus.INSECURE) {
|
||||||
|
signDisplayStatus = null;
|
||||||
|
encryptDisplayStatus = null;
|
||||||
|
}
|
||||||
|
if (signDisplayStatus == KeyDisplayStatus.OK) {
|
||||||
|
signDisplayStatus = null;
|
||||||
|
}
|
||||||
|
if (encryptDisplayStatus == KeyDisplayStatus.OK) {
|
||||||
|
encryptDisplayStatus = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.showExpandedState(certDisplayStatus, signDisplayStatus, encryptDisplayStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyDisplayStatus getKeyDisplayStatus(SubKeyItem subKeyItem) {
|
||||||
|
if (subKeyItem == null) {
|
||||||
|
return KeyDisplayStatus.UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subKeyItem.mIsRevoked) {
|
||||||
|
return KeyDisplayStatus.REVOKED;
|
||||||
|
}
|
||||||
|
if (subKeyItem.mIsExpired) {
|
||||||
|
return KeyDisplayStatus.EXPIRED;
|
||||||
|
}
|
||||||
|
if (subKeyItem.mSecurityProblem != null) {
|
||||||
|
return KeyDisplayStatus.INSECURE;
|
||||||
|
}
|
||||||
|
if (subKeyItem.mSecretKeyType == SecretKeyType.GNU_DUMMY) {
|
||||||
|
return KeyDisplayStatus.STRIPPED;
|
||||||
|
}
|
||||||
|
if (subKeyItem.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD) {
|
||||||
|
return KeyDisplayStatus.DIVERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyDisplayStatus.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum KeyHealthStatus {
|
||||||
|
OK, DIVERT, REVOKED, EXPIRED, INSECURE, SIGN_ONLY, STRIPPED, PARTIAL_STRIPPED, BROKEN
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KeyHealthMvpView {
|
||||||
|
void setKeyStatus(KeyHealthStatus keyHealthStatus);
|
||||||
|
void setPrimarySecurityProblem(KeySecurityProblem securityProblem);
|
||||||
|
void setPrimaryExpiryDate(Date expiry);
|
||||||
|
|
||||||
|
void setShowExpander(boolean showExpander);
|
||||||
|
void showExpandedState(KeyDisplayStatus certifyStatus, KeyDisplayStatus signStatus,
|
||||||
|
KeyDisplayStatus encryptStatus);
|
||||||
|
void hideExpandedInfo();
|
||||||
|
|
||||||
|
void setOnHealthClickListener(KeyHealthClickListener keyHealthClickListener);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KeyStatusMvpView {
|
||||||
|
void setCertifyStatus(KeyDisplayStatus unavailable);
|
||||||
|
void setSignStatus(KeyDisplayStatus signStatus);
|
||||||
|
void setDecryptStatus(KeyDisplayStatus encryptStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KeyHealthClickListener {
|
||||||
|
void onKeyHealthClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.ColorRes;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeyHealthPresenter.KeyStatusMvpView;
|
||||||
|
|
||||||
|
|
||||||
|
public class KeyStatusList extends LinearLayout implements KeyStatusMvpView {
|
||||||
|
private final TextView vCertText, vSignText, vDecryptText;
|
||||||
|
private final ImageView vCertIcon, vSignIcon, vDecryptIcon;
|
||||||
|
private final View vCertToken, vSignToken, vDecryptToken;
|
||||||
|
private final View vCertifyLayout, vSignLayout, vDecryptLayout;
|
||||||
|
|
||||||
|
public KeyStatusList(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
setOrientation(VERTICAL);
|
||||||
|
|
||||||
|
View view = LayoutInflater.from(context).inflate(R.layout.subkey_status_card_content, this, true);
|
||||||
|
|
||||||
|
vCertifyLayout = view.findViewById(R.id.cap_certify);
|
||||||
|
vSignLayout = view.findViewById(R.id.cap_sign);
|
||||||
|
vDecryptLayout = view.findViewById(R.id.cap_decrypt);
|
||||||
|
|
||||||
|
vCertText = (TextView) view.findViewById(R.id.cap_cert_text);
|
||||||
|
vSignText = (TextView) view.findViewById(R.id.cap_sign_text);
|
||||||
|
vDecryptText = (TextView) view.findViewById(R.id.cap_decrypt_text);
|
||||||
|
|
||||||
|
vCertIcon = (ImageView) view.findViewById(R.id.cap_cert_icon);
|
||||||
|
vSignIcon = (ImageView) view.findViewById(R.id.cap_sign_icon);
|
||||||
|
vDecryptIcon = (ImageView) view.findViewById(R.id.cap_decrypt_icon);
|
||||||
|
|
||||||
|
vCertToken = view.findViewById(R.id.cap_cert_security_token);
|
||||||
|
vSignToken = view.findViewById(R.id.cap_sign_security_token);
|
||||||
|
vDecryptToken = view.findViewById(R.id.cap_decrypt_security_token);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is just a list of statuses a key can be in, which we can also display
|
||||||
|
enum KeyDisplayStatus {
|
||||||
|
OK (R.color.android_green_light, R.color.primary,
|
||||||
|
R.string.cap_cert_ok, R.string.cap_sign_ok, R.string.cap_decrypt_ok, false),
|
||||||
|
DIVERT (R.color.android_green_light, R.color.primary,
|
||||||
|
R.string.cap_cert_divert, R.string.cap_sign_divert, R.string.cap_decrypt_divert, true),
|
||||||
|
REVOKED (R.color.android_red_light, R.color.android_red_light,
|
||||||
|
R.string.cap_sign_revoked, R.string.cap_decrypt_revoked, false),
|
||||||
|
EXPIRED (R.color.android_red_light, R.color.android_red_light,
|
||||||
|
R.string.cap_sign_expired, R.string.cap_decrypt_expired, false),
|
||||||
|
STRIPPED (R.color.android_red_light, R.color.android_red_light,
|
||||||
|
R.string.cap_cert_stripped, R.string.cap_sign_stripped, R.string.cap_decrypt_stripped, false),
|
||||||
|
INSECURE (R.color.android_red_light, R.color.android_red_light,
|
||||||
|
R.string.cap_sign_insecure, R.string.cap_sign_insecure, false),
|
||||||
|
UNAVAILABLE (R.color.android_red_light, R.color.android_red_light,
|
||||||
|
R.string.cap_cert_unavailable, R.string.cap_sign_unavailable, R.string.cap_decrypt_unavailable, false);
|
||||||
|
|
||||||
|
@ColorRes final int mColor, mTextColor;
|
||||||
|
@StringRes final Integer mCertifyStr, mSignStr, mDecryptStr;
|
||||||
|
final boolean mIsDivert;
|
||||||
|
|
||||||
|
KeyDisplayStatus(@ColorRes int color, @ColorRes int textColor,
|
||||||
|
@StringRes int signStr, @StringRes int encryptStr, boolean isDivert) {
|
||||||
|
mColor = color;
|
||||||
|
mTextColor = textColor;
|
||||||
|
mCertifyStr = null;
|
||||||
|
mSignStr = signStr;
|
||||||
|
mDecryptStr = encryptStr;
|
||||||
|
mIsDivert = isDivert;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyDisplayStatus(@ColorRes int color, @ColorRes int textColor,
|
||||||
|
@StringRes int certifyStr, @StringRes int signStr, @StringRes int encryptStr, boolean isDivert) {
|
||||||
|
mColor = color;
|
||||||
|
mTextColor = textColor;
|
||||||
|
mCertifyStr = certifyStr;
|
||||||
|
mSignStr = signStr;
|
||||||
|
mDecryptStr = encryptStr;
|
||||||
|
mIsDivert = isDivert;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCertifyStatus(KeyDisplayStatus keyDisplayStatus) {
|
||||||
|
if (keyDisplayStatus == null) {
|
||||||
|
vCertifyLayout.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vCertIcon.setColorFilter(getResources().getColor(keyDisplayStatus.mColor));
|
||||||
|
vCertText.setText(keyDisplayStatus.mCertifyStr);
|
||||||
|
vCertText.setTextColor(getResources().getColor(keyDisplayStatus.mTextColor));
|
||||||
|
vCertToken.setVisibility(keyDisplayStatus.mIsDivert ? View.VISIBLE : View.GONE);
|
||||||
|
vCertifyLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSignStatus(KeyDisplayStatus keyDisplayStatus) {
|
||||||
|
if (keyDisplayStatus == null) {
|
||||||
|
vSignLayout.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vSignIcon.setColorFilter(getResources().getColor(keyDisplayStatus.mColor));
|
||||||
|
vSignText.setText(keyDisplayStatus.mSignStr);
|
||||||
|
vSignText.setTextColor(getResources().getColor(keyDisplayStatus.mTextColor));
|
||||||
|
vSignToken.setVisibility(keyDisplayStatus.mIsDivert ? View.VISIBLE : View.GONE);
|
||||||
|
vSignLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDecryptStatus(KeyDisplayStatus keyDisplayStatus) {
|
||||||
|
if (keyDisplayStatus == null) {
|
||||||
|
vDecryptLayout.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vDecryptIcon.setColorFilter(getResources().getColor(keyDisplayStatus.mColor));
|
||||||
|
vDecryptText.setText(keyDisplayStatus.mDecryptStr);
|
||||||
|
vDecryptText.setTextColor(getResources().getColor(keyDisplayStatus.mTextColor));
|
||||||
|
vDecryptToken.setVisibility(keyDisplayStatus.mIsDivert ? View.VISIBLE : View.GONE);
|
||||||
|
vDecryptLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.ui.widget;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.SubkeyStatusLoader.KeySubkeyStatus;
|
||||||
|
|
||||||
|
|
||||||
|
class SubkeyStatusLoader extends AsyncTaskLoader<KeySubkeyStatus> {
|
||||||
|
public static final String[] PROJECTION = new String[] {
|
||||||
|
Keys.KEY_ID,
|
||||||
|
Keys.CREATION,
|
||||||
|
Keys.CAN_CERTIFY,
|
||||||
|
Keys.CAN_SIGN,
|
||||||
|
Keys.CAN_ENCRYPT,
|
||||||
|
Keys.HAS_SECRET,
|
||||||
|
Keys.EXPIRY,
|
||||||
|
Keys.IS_REVOKED,
|
||||||
|
Keys.ALGORITHM,
|
||||||
|
Keys.KEY_SIZE,
|
||||||
|
Keys.KEY_CURVE_OID
|
||||||
|
};
|
||||||
|
private static final int INDEX_KEY_ID = 0;
|
||||||
|
private static final int INDEX_CREATION = 1;
|
||||||
|
private static final int INDEX_CAN_CERTIFY = 2;
|
||||||
|
private static final int INDEX_CAN_SIGN = 3;
|
||||||
|
private static final int INDEX_CAN_ENCRYPT = 4;
|
||||||
|
private static final int INDEX_HAS_SECRET = 5;
|
||||||
|
private static final int INDEX_EXPIRY = 6;
|
||||||
|
private static final int INDEX_IS_REVOKED = 7;
|
||||||
|
private static final int INDEX_ALGORITHM = 8;
|
||||||
|
private static final int INDEX_KEY_SIZE = 9;
|
||||||
|
private static final int INDEX_KEY_CURVE_OID = 10;
|
||||||
|
|
||||||
|
|
||||||
|
private final ContentResolver contentResolver;
|
||||||
|
private final long masterKeyId;
|
||||||
|
private final Comparator<SubKeyItem> comparator;
|
||||||
|
|
||||||
|
private KeySubkeyStatus cachedResult;
|
||||||
|
|
||||||
|
|
||||||
|
SubkeyStatusLoader(Context context, ContentResolver contentResolver, long masterKeyId, Comparator<SubKeyItem> comparator) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
this.contentResolver = contentResolver;
|
||||||
|
this.masterKeyId = masterKeyId;
|
||||||
|
this.comparator = comparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeySubkeyStatus loadInBackground() {
|
||||||
|
Cursor cursor = contentResolver.query(Keys.buildKeysUri(masterKeyId), PROJECTION, null, null, null);
|
||||||
|
if (cursor == null) {
|
||||||
|
Log.e(Constants.TAG, "Error loading key items!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SubKeyItem keyCertify = null;
|
||||||
|
ArrayList<SubKeyItem> keysSign = new ArrayList<>();
|
||||||
|
ArrayList<SubKeyItem> keysEncrypt = new ArrayList<>();
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
SubKeyItem ski = new SubKeyItem(masterKeyId, cursor);
|
||||||
|
|
||||||
|
if (ski.mKeyId == masterKeyId) {
|
||||||
|
keyCertify = ski;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ski.mCanSign) {
|
||||||
|
keysSign.add(ski);
|
||||||
|
}
|
||||||
|
if (ski.mCanEncrypt) {
|
||||||
|
keysEncrypt.add(ski);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCertify == null) {
|
||||||
|
throw new IllegalStateException("Certification key must be set at this point, it's a bug otherwise!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(keysSign, comparator);
|
||||||
|
Collections.sort(keysEncrypt, comparator);
|
||||||
|
|
||||||
|
return new KeySubkeyStatus(keyCertify, keysSign, keysEncrypt);
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(KeySubkeyStatus keySubkeyStatus) {
|
||||||
|
cachedResult = keySubkeyStatus;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
super.deliverResult(keySubkeyStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (cachedResult != null) {
|
||||||
|
deliverResult(cachedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeContentChanged() || cachedResult == null) {
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeySubkeyStatus {
|
||||||
|
@NonNull
|
||||||
|
final SubKeyItem keyCertify;
|
||||||
|
final List<SubKeyItem> keysSign;
|
||||||
|
final List<SubKeyItem> keysEncrypt;
|
||||||
|
|
||||||
|
KeySubkeyStatus(@NonNull SubKeyItem keyCertify, List<SubKeyItem> keysSign, List<SubKeyItem> keysEncrypt) {
|
||||||
|
this.keyCertify = keyCertify;
|
||||||
|
this.keysSign = keysSign;
|
||||||
|
this.keysEncrypt = keysEncrypt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SubKeyItem {
|
||||||
|
final int mPosition;
|
||||||
|
final long mKeyId;
|
||||||
|
final Date mCreation;
|
||||||
|
final SecretKeyType mSecretKeyType;
|
||||||
|
final boolean mIsRevoked, mIsExpired;
|
||||||
|
final Date mExpiry;
|
||||||
|
final boolean mCanCertify, mCanSign, mCanEncrypt;
|
||||||
|
final KeySecurityProblem mSecurityProblem;
|
||||||
|
|
||||||
|
SubKeyItem(long masterKeyId, Cursor cursor) {
|
||||||
|
mPosition = cursor.getPosition();
|
||||||
|
|
||||||
|
mKeyId = cursor.getLong(INDEX_KEY_ID);
|
||||||
|
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
|
||||||
|
|
||||||
|
mSecretKeyType = SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET));
|
||||||
|
|
||||||
|
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||||
|
mExpiry = cursor.isNull(INDEX_EXPIRY) ? null : new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
|
||||||
|
mIsExpired = mExpiry != null && mExpiry.before(new Date());
|
||||||
|
|
||||||
|
mCanCertify = cursor.getInt(INDEX_CAN_CERTIFY) > 0;
|
||||||
|
mCanSign = cursor.getInt(INDEX_CAN_SIGN) > 0;
|
||||||
|
mCanEncrypt = cursor.getInt(INDEX_CAN_ENCRYPT) > 0;
|
||||||
|
|
||||||
|
int algorithm = cursor.getInt(INDEX_ALGORITHM);
|
||||||
|
Integer bitStrength = cursor.isNull(INDEX_KEY_SIZE) ? null : cursor.getInt(INDEX_KEY_SIZE);
|
||||||
|
String curveOid = cursor.getString(INDEX_KEY_CURVE_OID);
|
||||||
|
|
||||||
|
mSecurityProblem = PgpSecurityConstants.getKeySecurityProblem(
|
||||||
|
masterKeyId, mKeyId, algorithm, bitStrength, curveOid);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean newerThan(SubKeyItem other) {
|
||||||
|
return mCreation.after(other.mCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isValid() {
|
||||||
|
return !mIsRevoked && !mIsExpired;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
OpenKeychain/src/main/res/drawable-hdpi/broken_heart_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-hdpi/broken_heart_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 694 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/broken_heart_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-mdpi/broken_heart_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 495 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/broken_heart_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-xhdpi/broken_heart_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 860 B |
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/broken_heart_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-xxhdpi/broken_heart_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
OpenKeychain/src/main/res/drawable-xxxhdpi/broken_heart_24dp.png
Normal file
BIN
OpenKeychain/src/main/res/drawable-xxxhdpi/broken_heart_24dp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
164
OpenKeychain/src/main/res/layout/key_health_card_content.xml
Normal file
164
OpenKeychain/src/main/res/layout/key_health_card_content.xml
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<merge
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/CardViewHeader"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Key Status" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/key_health_layout"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/key_health_icon"
|
||||||
|
tools:src="@drawable/ic_check_white_24dp"
|
||||||
|
tools:tint="@color/android_green_light"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/key_health_title"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
tools:text="Healthy" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/key_health_subtitle"
|
||||||
|
tools:text="No key issues found." />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/key_health_expander"
|
||||||
|
android:src="@drawable/ic_expand_more_black_24dp"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dip"
|
||||||
|
android:id="@+id/key_health_divider"
|
||||||
|
android:background="?android:attr/listDivider" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:id="@+id/key_insecure_layout"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:text="Security Problem"
|
||||||
|
android:textAppearance="@style/SectionHeader"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/key_insecure_problem"
|
||||||
|
tools:text="@string/key_insecure_bitstrength_2048_problem" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:text="Suggested Solution"
|
||||||
|
android:textAppearance="@style/SectionHeader"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/key_insecure_solution"
|
||||||
|
tools:text="@string/key_insecure_bitstrength_2048_solution" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:id="@+id/key_expiry_layout"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/key_expiry_text"
|
||||||
|
android:textAppearance="?android:textAppearanceSmall"
|
||||||
|
tools:text="@string/key_expiry_text"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<org.sufficientlysecure.keychain.ui.widget.KeyStatusList
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/key_health_status_list"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
<!--tools:visibility="visible"-->
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</merge>
|
||||||
@@ -13,5 +13,4 @@
|
|||||||
android:name="org.sufficientlysecure.keychain.ui.LogDisplayFragment"
|
android:name="org.sufficientlysecure.keychain.ui.LogDisplayFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
172
OpenKeychain/src/main/res/layout/subkey_status_card_content.xml
Normal file
172
OpenKeychain/src/main/res/layout/subkey_status_card_content.xml
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<merge
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:showIn="@layout/tools_vertlin">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:id="@+id/cap_certify">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_cert_icon"
|
||||||
|
android:src="@drawable/ic_action_verified_cutout_24dp"
|
||||||
|
tools:tint="@color/android_green_light"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?attr/colorHeaderText"
|
||||||
|
android:text="@string/cap_title_confirm" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_cert_text"
|
||||||
|
tools:text="@string/cap_cert_ok" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_cert_security_token"
|
||||||
|
android:src="@drawable/yubi_icon_24dp"
|
||||||
|
android:layout_marginLeft="6dp"
|
||||||
|
android:layout_marginRight="6dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:id="@+id/cap_sign">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_sign_icon"
|
||||||
|
android:src="@drawable/ic_mode_edit_white_24dp"
|
||||||
|
tools:tint="@color/android_green_light"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_sign_caption"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?attr/colorHeaderText"
|
||||||
|
android:text="@string/cap_title_sign" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_sign_text"
|
||||||
|
tools:text="@string/cap_sign_divert" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_sign_security_token"
|
||||||
|
android:src="@drawable/yubi_icon_24dp"
|
||||||
|
android:layout_marginLeft="6dp"
|
||||||
|
android:layout_marginRight="6dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:id="@+id/cap_decrypt">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_decrypt_icon"
|
||||||
|
android:src="@drawable/ic_lock_white_24dp"
|
||||||
|
tools:tint="@color/android_red_light"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?attr/colorHeaderText"
|
||||||
|
android:text="@string/cap_title_decrypt" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_decrypt_text"
|
||||||
|
tools:textColor="@color/android_red_light"
|
||||||
|
tools:text="@string/cap_decrypt_unavailable" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/cap_decrypt_security_token"
|
||||||
|
android:src="@drawable/yubi_icon_24dp"
|
||||||
|
android:layout_marginLeft="6dp"
|
||||||
|
android:layout_marginRight="6dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</merge>
|
||||||
9
OpenKeychain/src/main/res/layout/tools_vertlin.xml
Normal file
9
OpenKeychain/src/main/res/layout/tools_vertlin.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<include layout="@layout/subkey_status_card_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -9,6 +9,19 @@
|
|||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:paddingTop="16dp">
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<org.sufficientlysecure.keychain.ui.widget.KeyHealthCardView
|
||||||
|
android:id="@+id/subkey_status_card"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
card_view:cardBackgroundColor="?attr/colorCardViewBackground"
|
||||||
|
card_view:cardElevation="2dp"
|
||||||
|
card_view:cardUseCompatPadding="true"
|
||||||
|
card_view:cardCornerRadius="4dp"
|
||||||
|
/>
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
<android.support.v7.widget.CardView
|
||||||
android:id="@+id/card_linked_ids"
|
android:id="@+id/card_linked_ids"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -121,6 +121,7 @@
|
|||||||
<string name="menu_help">"Help"</string>
|
<string name="menu_help">"Help"</string>
|
||||||
<string name="menu_export_key">"Backup key"</string>
|
<string name="menu_export_key">"Backup key"</string>
|
||||||
<string name="menu_delete_key">"Delete key"</string>
|
<string name="menu_delete_key">"Delete key"</string>
|
||||||
|
<string name="menu_status">"View key status"</string>
|
||||||
<string name="menu_manage_keys">"Manage my keys"</string>
|
<string name="menu_manage_keys">"Manage my keys"</string>
|
||||||
<string name="menu_search">"Search"</string>
|
<string name="menu_search">"Search"</string>
|
||||||
<string name="menu_open">"Open"</string>
|
<string name="menu_open">"Open"</string>
|
||||||
@@ -815,10 +816,6 @@
|
|||||||
<string name="create_key_yubi_key_algorithm_auth_text">Authentication key</string>
|
<string name="create_key_yubi_key_algorithm_auth_text">Authentication key</string>
|
||||||
|
|
||||||
<!-- View key -->
|
<!-- View key -->
|
||||||
<string name="view_key_revoked">"Revoked: Key must not be used anymore!"</string>
|
|
||||||
<string name="view_key_insecure">"Insecure: Key must not be used anymore!"</string>
|
|
||||||
<string name="view_key_expired">"Expired: The contact needs to extend the key's validity!"</string>
|
|
||||||
<string name="view_key_expired_secret">"Expired: You can extend the keys validity by editing it!"</string>
|
|
||||||
<string name="view_key_my_key">"My Key"</string>
|
<string name="view_key_my_key">"My Key"</string>
|
||||||
<string name="view_key_verified">"Confirmed Key"</string>
|
<string name="view_key_verified">"Confirmed Key"</string>
|
||||||
<string name="view_key_unverified">"Unconfirmed: Scan QR Code to confirm key!"</string>
|
<string name="view_key_unverified">"Unconfirmed: Scan QR Code to confirm key!"</string>
|
||||||
@@ -1798,4 +1795,62 @@
|
|||||||
<string name="requested_key_label">Requested key:</string>
|
<string name="requested_key_label">Requested key:</string>
|
||||||
<string name="error_preselect_sign_key">Error selecting key %s for signing!</string>
|
<string name="error_preselect_sign_key">Error selecting key %s for signing!</string>
|
||||||
<string name="error_preselect_encrypt_key">Error selecting key %s for encryption!</string>
|
<string name="error_preselect_encrypt_key">Error selecting key %s for encryption!</string>
|
||||||
|
|
||||||
|
<string name="title_key_status">"Key Status"</string>
|
||||||
|
<string name="caption_secret_status">This key is yours. You can use it to:</string>
|
||||||
|
|
||||||
|
<string name="cap_title_confirm">Confirm other keys</string>
|
||||||
|
<string name="cap_cert_ok">"This key can confirm other keys."</string>
|
||||||
|
<string name="cap_cert_divert">"This key can confirm other keys, using a Security Token."</string>
|
||||||
|
<string name="cap_cert_stripped">"This key is stripped, it can NOT confirm other keys."</string>
|
||||||
|
<string name="cap_cert_unavailable">"This key is not configured to confirm keys!"</string>
|
||||||
|
|
||||||
|
<string name="cap_title_sign">Sign messages</string>
|
||||||
|
<string name="cap_sign_ok">"This key can sign/send messages."</string>
|
||||||
|
<string name="cap_sign_divert">"This key can sign/send messages, using a Security Token."</string>
|
||||||
|
<string name="cap_sign_expired">"This key can't sign/send messages, because it is expired."</string>
|
||||||
|
<string name="cap_sign_revoked">"This key can't sign/send messages, because it is revoked."</string>
|
||||||
|
<string name="cap_sign_stripped">"This key can\'t sign/send messages on this device!"</string>
|
||||||
|
<string name="cap_sign_unavailable">"This key is not configured to sign/send messages!"</string>
|
||||||
|
<string name="cap_sign_insecure">"This key can sign/send messages, but not securely!"</string>
|
||||||
|
|
||||||
|
<string name="cap_title_decrypt">Decrypt messages</string>
|
||||||
|
<string name="cap_decrypt_ok">"This key can decrypt/receive messages."</string>
|
||||||
|
<string name="cap_decrypt_divert">"This key can decrypt/receive messages, using a Security Token."</string>
|
||||||
|
<string name="cap_decrypt_expired">"This key can decrypt/receive messages, but is expired."</string>
|
||||||
|
<string name="cap_decrypt_revoked">"This key can decrypt/receive messages, but is revoked."</string>
|
||||||
|
<string name="cap_decrypt_stripped">"This key can\'t decrypt/receive messages on this device."</string>
|
||||||
|
<string name="cap_decrypt_unavailable">"This key is not configured to decrypt/receive messages!"</string>
|
||||||
|
<string name="cap_decrypt_insecure">"This key can decrypt/receive messages, but not securely!"</string>
|
||||||
|
|
||||||
|
<string name="key_health_ok_title">"Healthy"</string>
|
||||||
|
<string name="key_health_ok_subtitle">"No key issues found."</string>
|
||||||
|
<string name="key_health_divert_title">"Healthy (Security Token)"</string>
|
||||||
|
<string name="key_health_divert_subtitle">"No key issues found."</string>
|
||||||
|
<string name="key_health_expired_title">"Expired"</string>
|
||||||
|
<string name="key_health_expired_subtitle">"This key should not be used anymore."</string>
|
||||||
|
<string name="key_health_revoked_title">"Revoked"</string>
|
||||||
|
<string name="key_health_revoked_subtitle">"This key can\'t be used anymore."</string>
|
||||||
|
<string name="key_health_insecure_title">"Insecure"</string>
|
||||||
|
<string name="key_health_insecure_subtitle">"This key is not secure!"</string>
|
||||||
|
<string name="key_health_broken_title">"Defective"</string>
|
||||||
|
<string name="key_health_broken_subtitle">"Click for details"</string>
|
||||||
|
<string name="key_health_sign_only_title">"Healthy (Signing Key)"</string>
|
||||||
|
<string name="key_health_sign_only_subtitle">"Click for details"</string>
|
||||||
|
<string name="key_health_stripped_title">"Healthy (Stripped)"</string>
|
||||||
|
<string name="key_health_stripped_subtitle">"Click for details"</string>
|
||||||
|
<string name="key_health_partial_stripped_title">"Healthy (Partially Stripped)"</string>
|
||||||
|
<string name="key_health_partial_stripped_subtitle">"Click for details"</string>
|
||||||
|
|
||||||
|
<string name="key_insecure_bitstrength_2048_problem">"This key uses the <b>%1$s</b> algorithm with a strength of <b>%2$s bits</b>. A secure key should have a strength of 2048 bits."</string>
|
||||||
|
<string name="key_insecure_bitstrength_2048_solution">"This key can\'t be upgraded. For secure communication, the owner must generate a new key."</string>
|
||||||
|
|
||||||
|
<string name="key_insecure_unknown_curve_problem">"This key uses the <b>%1$s</b> algorithm, which is not whitelisted."</string>
|
||||||
|
<string name="key_insecure_unknown_curve_solution">"This key can\'t be upgraded. For secure communication, the owner must generate a new key."</string>
|
||||||
|
|
||||||
|
<string name="key_insecure_unidentified_problem">"There is an unidentified problem with this key."</string>
|
||||||
|
<string name="key_insecure_unidentified_solution">"Please submit a bug report."</string>
|
||||||
|
|
||||||
|
<string name="key_expiry_text">"This key expired on <b>%1$s</b>."</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
3
graphics/drawables/broken_heart.svg
Normal file
3
graphics/drawables/broken_heart.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||||
|
<path fill="#000000" d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C8.17,3 8.82,3.12 9.44,3.33L13,9.35L9,14.35L12,21.35V21.35M16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35L11,14.35L15.5,9.35L12.85,4.27C13.87,3.47 15.17,3 16.5,3Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 348 B |
@@ -22,7 +22,7 @@ SRC_DIR=./drawables/
|
|||||||
#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg
|
#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg
|
||||||
|
|
||||||
|
|
||||||
for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_paste" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link"
|
for NAME in "broken_heart" "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_paste" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link"
|
||||||
do
|
do
|
||||||
echo $NAME
|
echo $NAME
|
||||||
inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
|
inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
|
||||||
|
|||||||
Reference in New Issue
Block a user