Merge branch 'master' into ditch-appmsg
Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
This commit is contained in:
@@ -68,6 +68,8 @@ public final class Constants {
|
||||
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
|
||||
public static final String KEY_SERVERS = "keyServers";
|
||||
public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion";
|
||||
public static final String CONCEAL_PGP_APPLICATION = "concealPgpApplication";
|
||||
public static final String FIRST_TIME = "firstTime";
|
||||
}
|
||||
|
||||
public static final class Defaults {
|
||||
|
||||
@@ -139,6 +139,16 @@ public class Preferences {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean isFirstTime() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
|
||||
}
|
||||
|
||||
public void setFirstTime(boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.Pref.FIRST_TIME, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String[] getKeyServers() {
|
||||
String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,
|
||||
Constants.Defaults.KEY_SERVERS);
|
||||
@@ -187,4 +197,14 @@ public class Preferences {
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public void setConcealPgpApplication(boolean conceal) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.Pref.CONCEAL_PGP_APPLICATION, conceal);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getConcealPgpApplication() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.CONCEAL_PGP_APPLICATION, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
|
||||
mHashCode = key.hashCode();
|
||||
|
||||
mPrimaryUserId = key.getPrimaryUserId();
|
||||
mPrimaryUserId = key.getPrimaryUserIdWithFallback();
|
||||
mUserIds = key.getUnorderedUserIds();
|
||||
|
||||
// if there was no user id flagged as primary, use the first one
|
||||
|
||||
@@ -22,8 +22,10 @@ public abstract class KeyRing {
|
||||
|
||||
abstract public String getPrimaryUserId() throws PgpGeneralException;
|
||||
|
||||
public String[] getSplitPrimaryUserId() throws PgpGeneralException {
|
||||
return splitUserId(getPrimaryUserId());
|
||||
abstract public String getPrimaryUserIdWithFallback() throws PgpGeneralException;
|
||||
|
||||
public String[] getSplitPrimaryUserIdWithFallback() throws PgpGeneralException {
|
||||
return splitUserId(getPrimaryUserIdWithFallback());
|
||||
}
|
||||
|
||||
abstract public boolean isRevoked() throws PgpGeneralException;
|
||||
|
||||
@@ -258,7 +258,7 @@ public class PgpDecryptVerify {
|
||||
continue;
|
||||
}
|
||||
// get subkey which has been used for this encryption packet
|
||||
secretEncryptionKey = secretKeyRing.getSubKey(encData.getKeyID());
|
||||
secretEncryptionKey = secretKeyRing.getSecretKey(encData.getKeyID());
|
||||
if (secretEncryptionKey == null) {
|
||||
// continue with the next packet in the while loop
|
||||
continue;
|
||||
@@ -393,7 +393,7 @@ public class PgpDecryptVerify {
|
||||
signingRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getSubkey(sigKeyId);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found!");
|
||||
@@ -409,7 +409,7 @@ public class PgpDecryptVerify {
|
||||
signatureResultBuilder.knownKey(true);
|
||||
signatureResultBuilder.keyId(signingRing.getMasterKeyId());
|
||||
try {
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserId());
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback());
|
||||
} catch(PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId());
|
||||
}
|
||||
@@ -578,7 +578,7 @@ public class PgpDecryptVerify {
|
||||
signingRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getSubkey(sigKeyId);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found!");
|
||||
@@ -596,7 +596,7 @@ public class PgpDecryptVerify {
|
||||
signatureResultBuilder.knownKey(true);
|
||||
signatureResultBuilder.keyId(signingRing.getMasterKeyId());
|
||||
try {
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserId());
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback());
|
||||
} catch(PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId());
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
@@ -60,7 +61,11 @@ public class PgpHelper {
|
||||
}
|
||||
|
||||
public static String getFullVersion(Context context) {
|
||||
return "OpenKeychain v" + getVersion(context);
|
||||
if(Preferences.getPreferences(context).getConcealPgpApplication()){
|
||||
return "";
|
||||
} else {
|
||||
return "OpenKeychain v" + getVersion(context);
|
||||
}
|
||||
}
|
||||
|
||||
// public static final class content {
|
||||
|
||||
@@ -65,10 +65,8 @@ import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* This class is the single place where ALL operations that actually modify a PGP public or secret
|
||||
@@ -104,11 +102,12 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
/** Creates new secret key. */
|
||||
private PGPKeyPair createKey(int algorithmChoice, int keySize) throws PgpGeneralMsgIdException {
|
||||
private PGPKeyPair createKey(int algorithmChoice, int keySize, OperationLog log, int indent) {
|
||||
|
||||
try {
|
||||
if (keySize < 512) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
int algorithm;
|
||||
@@ -143,7 +142,8 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,9 @@ public class PgpKeyOperation {
|
||||
} catch(InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch(PGPException e) {
|
||||
throw new PgpGeneralMsgIdException(R.string.msg_mf_error_pgp, e);
|
||||
Log.e(Constants.TAG, "internal pgp error", e);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,20 +168,36 @@ public class PgpKeyOperation {
|
||||
|
||||
try {
|
||||
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent);
|
||||
log.add(LogLevel.START, LogType.MSG_CR, indent);
|
||||
indent += 1;
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
|
||||
if (saveParcel.addSubKeys == null || saveParcel.addSubKeys.isEmpty()) {
|
||||
if (saveParcel.mAddSubKeys.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_MASTER, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
SubkeyAdd add = saveParcel.addSubKeys.remove(0);
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
|
||||
if (saveParcel.mAddUserIds.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_USER_ID, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
SubkeyAdd add = saveParcel.mAddSubKeys.remove(0);
|
||||
if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CERTIFY, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (add.mAlgorithm == Constants.choice.algorithm.elgamal) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
|
||||
// return null if this failed (an error will already have been logged by createKey)
|
||||
if (keyPair == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// define hashing and signing algos
|
||||
@@ -195,17 +213,15 @@ public class PgpKeyOperation {
|
||||
PGPSecretKeyRing sKR = new PGPSecretKeyRing(
|
||||
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
|
||||
|
||||
return internal(sKR, masterSecretKey, saveParcel, "", log, indent);
|
||||
return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log, indent);
|
||||
|
||||
} catch (PGPException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
Log.e(Constants.TAG, "pgp error encoding key", e);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "io error encoding key", e);
|
||||
return null;
|
||||
} catch (PgpGeneralMsgIdException e) {
|
||||
Log.e(Constants.TAG, "pgp msg id error", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -257,11 +273,16 @@ public class PgpKeyOperation {
|
||||
return null;
|
||||
}
|
||||
|
||||
return internal(sKR, masterSecretKey, saveParcel, passphrase, log, indent);
|
||||
// read masterKeyFlags, and use the same as before.
|
||||
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||
int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER;
|
||||
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log, indent);
|
||||
|
||||
}
|
||||
|
||||
private UncachedKeyRing internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
int masterKeyFlags,
|
||||
SaveKeyringParcel saveParcel, String passphrase,
|
||||
OperationLog log, int indent) {
|
||||
|
||||
@@ -289,18 +310,24 @@ public class PgpKeyOperation {
|
||||
PGPPublicKey modifiedPublicKey = masterPublicKey;
|
||||
|
||||
// 2a. Add certificates for new user ids
|
||||
for (String userId : saveParcel.addUserIds) {
|
||||
for (String userId : saveParcel.mAddUserIds) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);
|
||||
|
||||
if (userId.equals("")) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1);
|
||||
return null;
|
||||
}
|
||||
|
||||
// this operation supersedes all previous binding and revocation certificates,
|
||||
// so remove those to retain assertions from canonicalization for later operations
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
|
||||
if (it != null) {
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) {
|
||||
// if it's not a self cert, never mind
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
continue;
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
}
|
||||
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
|
||||
|| cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
|
||||
@@ -314,27 +341,31 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// if it's supposed to be primary, we can do that here as well
|
||||
boolean isPrimary = saveParcel.changePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.changePrimaryUserId);
|
||||
boolean isPrimary = saveParcel.mChangePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.mChangePrimaryUserId);
|
||||
// generate and add new certificate
|
||||
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
|
||||
masterPublicKey, userId, isPrimary);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
|
||||
masterPublicKey, userId, isPrimary, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
|
||||
// 2b. Add revocations for revoked user ids
|
||||
for (String userId : saveParcel.revokeUserIds) {
|
||||
for (String userId : saveParcel.mRevokeUserIds) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent);
|
||||
// a duplicate revocatin will be removed during canonicalization, so no need to
|
||||
// a duplicate revocation will be removed during canonicalization, so no need to
|
||||
// take care of that here.
|
||||
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
|
||||
masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
|
||||
// 3. If primary user id changed, generate new certificates for both old and new
|
||||
if (saveParcel.changePrimaryUserId != null) {
|
||||
if (saveParcel.mChangePrimaryUserId != null) {
|
||||
|
||||
// keep track if we actually changed one
|
||||
boolean ok = false;
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent);
|
||||
indent += 1;
|
||||
|
||||
// we work on the modifiedPublicKey here, to respect new or newly revoked uids
|
||||
// noinspection unchecked
|
||||
@@ -343,10 +374,11 @@ public class PgpKeyOperation {
|
||||
PGPSignature currentCert = null;
|
||||
// noinspection unchecked
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(
|
||||
masterPublicKey.getSignaturesForID(userId))) {
|
||||
// if it's not a self cert, never mind
|
||||
modifiedPublicKey.getSignaturesForID(userId))) {
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
continue;
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
}
|
||||
// we know from canonicalization that if there is any revocation here, it
|
||||
// is valid and not superseded by a newer certification.
|
||||
@@ -373,7 +405,7 @@ public class PgpKeyOperation {
|
||||
// we definitely should not update certifications of revoked keys, so just leave it.
|
||||
if (isRevoked) {
|
||||
// revoked user ids cannot be primary!
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
|
||||
return null;
|
||||
}
|
||||
@@ -383,14 +415,16 @@ public class PgpKeyOperation {
|
||||
// if this is~ the/a primary user id
|
||||
if (currentCert.hasSubpackets() && currentCert.getHashedSubPackets().isPrimaryUserID()) {
|
||||
// if it's the one we want, just leave it as is
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
ok = true;
|
||||
continue;
|
||||
}
|
||||
// otherwise, generate new non-primary certification
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, false);
|
||||
masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
continue;
|
||||
@@ -399,20 +433,28 @@ public class PgpKeyOperation {
|
||||
// if we are here, this is not currently a primary user id
|
||||
|
||||
// if it should be
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
// add shiny new primary user id certificate
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_NEW, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, true);
|
||||
masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
ok = true;
|
||||
}
|
||||
|
||||
// user id is not primary and is not supposed to be - nothing to do here.
|
||||
|
||||
}
|
||||
|
||||
indent -= 1;
|
||||
|
||||
if (!ok) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the secret key ring
|
||||
@@ -423,9 +465,16 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// 4a. For each subkey change, generate new subkey binding certificate
|
||||
for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) {
|
||||
for (SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
|
||||
// TODO allow changes in master key? this implies generating new user id certs...
|
||||
if (change.mKeyId == masterPublicKey.getKeyID()) {
|
||||
Log.e(Constants.TAG, "changing the master key not supported");
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
|
||||
if (sKey == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
|
||||
@@ -434,22 +483,45 @@ public class PgpKeyOperation {
|
||||
}
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) {
|
||||
// expiry must not be in the past
|
||||
if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,
|
||||
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
return null;
|
||||
}
|
||||
|
||||
// generate and add new signature. we can be sloppy here and just leave the old one,
|
||||
// it will be removed during canonicalization
|
||||
// keep old flags, or replace with new ones
|
||||
int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags;
|
||||
long expiry;
|
||||
if (change.mExpiry == null) {
|
||||
long valid = pKey.getValidSeconds();
|
||||
expiry = valid == 0
|
||||
? 0
|
||||
: pKey.getCreationTime().getTime() / 1000 + pKey.getValidSeconds();
|
||||
} else {
|
||||
expiry = change.mExpiry;
|
||||
}
|
||||
|
||||
// drop all old signatures, they will be superseded by the new one
|
||||
//noinspection unchecked
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) {
|
||||
// special case: if there is a revocation, don't use expiry from before
|
||||
if (change.mExpiry == null
|
||||
&& sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) {
|
||||
expiry = 0;
|
||||
}
|
||||
pKey = PGPPublicKey.removeCertification(pKey, sig);
|
||||
}
|
||||
|
||||
// generate and add new signature
|
||||
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
|
||||
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
|
||||
sKey, pKey, flags, expiry, passphrase);
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
|
||||
// 4b. For each subkey revocation, generate new subkey revocation certificate
|
||||
for (long revocation : saveParcel.revokeSubKeys) {
|
||||
for (long revocation : saveParcel.mRevokeSubKeys) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(revocation));
|
||||
PGPSecretKey sKey = sKR.getSecretKey(revocation);
|
||||
@@ -468,52 +540,51 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// 5. Generate and add new subkeys
|
||||
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) {
|
||||
try {
|
||||
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.mAddSubKeys) {
|
||||
|
||||
if (add.mExpiry != null && new Date(add.mExpiry).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
// generate a new secret key (privkey only for now)
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
|
||||
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
|
||||
sha1Calc, false, keyEncryptor);
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
|
||||
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
} catch (PgpGeneralMsgIdException e) {
|
||||
if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
// generate a new secret key (privkey only for now)
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
if(keyPair == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry == null ? 0 : add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
|
||||
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
|
||||
sha1Calc, false, keyEncryptor);
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
|
||||
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
}
|
||||
|
||||
// 6. If requested, change passphrase
|
||||
if (saveParcel.newPassphrase != null) {
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
|
||||
.get(HashAlgorithmTags.SHA1);
|
||||
@@ -523,7 +594,7 @@ public class PgpKeyOperation {
|
||||
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.newPassphrase.toCharArray());
|
||||
saveParcel.mNewPassphrase.toCharArray());
|
||||
|
||||
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
|
||||
}
|
||||
@@ -533,6 +604,7 @@ public class PgpKeyOperation {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_ENCODE, indent+1);
|
||||
return null;
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "encountered pgp error while modifying key", e);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent+1);
|
||||
return null;
|
||||
} catch (SignatureException e) {
|
||||
@@ -546,7 +618,7 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
private static PGPSignature generateUserIdSignature(
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary)
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
pKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
@@ -558,6 +630,7 @@ public class PgpKeyOperation {
|
||||
subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||
subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||
subHashedPacketsGen.setPrimaryUserID(false, primary);
|
||||
subHashedPacketsGen.setKeyFlags(false, flags);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
return sGen.generateCertification(userId, pKey);
|
||||
@@ -599,7 +672,7 @@ public class PgpKeyOperation {
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, Long expiry, String passphrase)
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, String passphrase)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
@@ -611,7 +684,7 @@ public class PgpKeyOperation {
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, Long expiry)
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
|
||||
// date for signing
|
||||
@@ -640,17 +713,9 @@ public class PgpKeyOperation {
|
||||
hashedPacketsGen.setKeyFlags(false, flags);
|
||||
}
|
||||
|
||||
if (expiry != null) {
|
||||
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
creationDate.setTime(pKey.getCreationTime());
|
||||
|
||||
// (Just making sure there's no programming error here, this MUST have been checked above!)
|
||||
if (new Date(expiry).before(todayDate)) {
|
||||
throw new RuntimeException("Bad subkey creation date, this is a bug!");
|
||||
}
|
||||
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis());
|
||||
} else {
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
if (expiry > 0) {
|
||||
long creationTime = pKey.getCreationTime().getTime() / 1000;
|
||||
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime);
|
||||
}
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
@@ -665,4 +730,22 @@ public class PgpKeyOperation {
|
||||
|
||||
}
|
||||
|
||||
/** Returns all flags valid for this key.
|
||||
*
|
||||
* This method does not do any validity checks on the signature, so it should not be used on
|
||||
* a non-canonicalized key!
|
||||
*
|
||||
*/
|
||||
private static int readKeyFlags(PGPPublicKey key) {
|
||||
int flags = 0;
|
||||
//noinspection unchecked
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (!sig.hasSubpackets()) {
|
||||
continue;
|
||||
}
|
||||
flags |= sig.getHashedSubPackets().getKeyFlags();
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
@@ -22,12 +23,11 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
@@ -81,11 +81,15 @@ public class UncachedKeyRing {
|
||||
return new UncachedPublicKey(mRing.getPublicKey());
|
||||
}
|
||||
|
||||
public UncachedPublicKey getPublicKey(long keyId) {
|
||||
return new UncachedPublicKey(mRing.getPublicKey(keyId));
|
||||
}
|
||||
|
||||
public Iterator<UncachedPublicKey> getPublicKeys() {
|
||||
final Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
||||
return new Iterator<UncachedPublicKey>() {
|
||||
public void remove() {
|
||||
it.remove();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public UncachedPublicKey next() {
|
||||
return new UncachedPublicKey(it.next());
|
||||
@@ -115,42 +119,41 @@ public class UncachedKeyRing {
|
||||
|
||||
public static UncachedKeyRing decodeFromData(byte[] data)
|
||||
throws PgpGeneralException, IOException {
|
||||
BufferedInputStream bufferedInput =
|
||||
new BufferedInputStream(new ByteArrayInputStream(data));
|
||||
if (bufferedInput.available() > 0) {
|
||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
|
||||
// get first object in block
|
||||
Object obj;
|
||||
if ((obj = objectFactory.nextObject()) != null && obj instanceof PGPKeyRing) {
|
||||
return new UncachedKeyRing((PGPKeyRing) obj);
|
||||
} else {
|
||||
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
} else {
|
||||
List<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data));
|
||||
|
||||
if (parsed.isEmpty()) {
|
||||
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
if (parsed.size() > 1) {
|
||||
throw new PgpGeneralException(
|
||||
"Expected single keyring in stream, found " + parsed.size());
|
||||
}
|
||||
|
||||
return parsed.get(0);
|
||||
|
||||
}
|
||||
|
||||
public static List<UncachedKeyRing> fromStream(InputStream stream)
|
||||
throws PgpGeneralException, IOException {
|
||||
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
|
||||
|
||||
List<UncachedKeyRing> result = new Vector<UncachedKeyRing>();
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
while(stream.available() > 0) {
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
if (!(obj instanceof PGPKeyRing)) {
|
||||
throw new PgpGeneralException(
|
||||
"Bad object of type " + obj.getClass().getName() + " in stream!");
|
||||
}
|
||||
result.add(new UncachedKeyRing((PGPKeyRing) obj));
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -189,7 +192,7 @@ public class UncachedKeyRing {
|
||||
* - Remove all certificates flagged as "local"
|
||||
* - Remove all certificates which are superseded by a newer one on the same target,
|
||||
* including revocations with later re-certifications.
|
||||
* - Remove all certificates of unknown type:
|
||||
* - Remove all certificates in other positions if not of known type:
|
||||
* - key revocation signatures on the master key
|
||||
* - subkey binding signatures for subkeys
|
||||
* - certifications and certification revocations for user ids
|
||||
@@ -225,7 +228,7 @@ public class UncachedKeyRing {
|
||||
|
||||
PGPPublicKey modified = masterKey;
|
||||
PGPSignature revocation = null;
|
||||
for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getSignatures())) {
|
||||
for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) {
|
||||
int type = zert.getSignatureType();
|
||||
|
||||
// Disregard certifications on user ids, we will deal with those later
|
||||
@@ -234,6 +237,10 @@ public class UncachedKeyRing {
|
||||
|| type == PGPSignature.CASUAL_CERTIFICATION
|
||||
|| type == PGPSignature.POSITIVE_CERTIFICATION
|
||||
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
// These should not be here...
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
WrappedSignature cert = new WrappedSignature(zert);
|
||||
@@ -317,7 +324,7 @@ public class UncachedKeyRing {
|
||||
|
||||
if (cert.getCreationTime().after(now)) {
|
||||
// Creation date in the future? No way!
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, indent);
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TIME, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
@@ -325,7 +332,7 @@ public class UncachedKeyRing {
|
||||
|
||||
if (cert.isLocal()) {
|
||||
// Creation date in the future? No way!
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, indent);
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_LOCAL, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
@@ -425,7 +432,7 @@ public class UncachedKeyRing {
|
||||
// If no valid certificate (if only a revocation) remains, drop it
|
||||
if (selfCert == null && revocation == null) {
|
||||
modified = PGPPublicKey.removeCertification(modified, userId);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REVOKE_DUP,
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REMOVE,
|
||||
indent, userId);
|
||||
}
|
||||
}
|
||||
@@ -505,14 +512,16 @@ public class UncachedKeyRing {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this certificate says it allows signing for the key
|
||||
if (zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
|
||||
|
||||
int flags = ((KeyFlags) zert.getHashedSubPackets()
|
||||
.getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags();
|
||||
// If this subkey is allowed to sign data,
|
||||
if ((flags & PGPKeyFlags.CAN_SIGN) == PGPKeyFlags.CAN_SIGN) {
|
||||
boolean ok = false;
|
||||
// it MUST have an embedded primary key binding signature
|
||||
try {
|
||||
PGPSignatureList list = zert.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
boolean ok = false;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
WrappedSignature subsig = new WrappedSignature(list.get(i));
|
||||
if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
|
||||
@@ -526,17 +535,19 @@ public class UncachedKeyRing {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
// if it doesn't, get rid of this!
|
||||
if (!ok) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if we already have a cert, and this one is not newer: skip it
|
||||
@@ -549,6 +560,8 @@ public class UncachedKeyRing {
|
||||
selfCert = zert;
|
||||
// if this is newer than a possibly existing revocation, drop that one
|
||||
if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_REVOKE_DUP, indent);
|
||||
redundantCerts += 1;
|
||||
revocation = null;
|
||||
}
|
||||
|
||||
@@ -558,7 +571,7 @@ public class UncachedKeyRing {
|
||||
// make sure the certificate checks out
|
||||
try {
|
||||
cert.init(masterKey);
|
||||
if (!cert.verifySignature(key)) {
|
||||
if (!cert.verifySignature(masterKey, key)) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
@@ -582,7 +595,7 @@ public class UncachedKeyRing {
|
||||
|
||||
// it is not properly bound? error!
|
||||
if (selfCert == null) {
|
||||
ring = replacePublicKey(ring, modified);
|
||||
ring = removeSubKey(ring, key);
|
||||
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(key.getKeyID()));
|
||||
@@ -650,7 +663,7 @@ public class UncachedKeyRing {
|
||||
return left.length - right.length;
|
||||
}
|
||||
// compare byte-by-byte
|
||||
for (int i = 0; i < left.length && i < right.length; i++) {
|
||||
for (int i = 0; i < left.length; i++) {
|
||||
if (left[i] != right[i]) {
|
||||
return (left[i] & 0xff) - (right[i] & 0xff);
|
||||
}
|
||||
@@ -678,7 +691,14 @@ public class UncachedKeyRing {
|
||||
final PGPPublicKey resultKey = result.getPublicKey(key.getKeyID());
|
||||
if (resultKey == null) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, indent);
|
||||
result = replacePublicKey(result, key);
|
||||
// special case: if both rings are secret, copy over the secret key
|
||||
if (isSecret() && other.isSecret()) {
|
||||
PGPSecretKey sKey = ((PGPSecretKeyRing) candidate).getSecretKey(key.getKeyID());
|
||||
result = PGPSecretKeyRing.insertSecretKey((PGPSecretKeyRing) result, sKey);
|
||||
} else {
|
||||
// otherwise, just insert the public key
|
||||
result = replacePublicKey(result, key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -686,17 +706,7 @@ public class UncachedKeyRing {
|
||||
PGPPublicKey modified = resultKey;
|
||||
|
||||
// Iterate certifications
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
int type = cert.getSignatureType();
|
||||
// Disregard certifications on user ids, we will deal with those later
|
||||
if (type == PGPSignature.NO_CERTIFICATION
|
||||
|| type == PGPSignature.DEFAULT_CERTIFICATION
|
||||
|| type == PGPSignature.CASUAL_CERTIFICATION
|
||||
|| type == PGPSignature.POSITIVE_CERTIFICATION
|
||||
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getKeySignatures())) {
|
||||
// Don't merge foreign stuff into secret keys
|
||||
if (cert.getKeyID() != masterKeyId && isSecret()) {
|
||||
continue;
|
||||
@@ -744,8 +754,12 @@ public class UncachedKeyRing {
|
||||
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW,
|
||||
indent, Integer.toString(newCerts));
|
||||
if (newCerts > 0) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW, indent,
|
||||
Integer.toString(newCerts));
|
||||
} else {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_UNCHANGED, indent);
|
||||
}
|
||||
|
||||
return new UncachedKeyRing(result);
|
||||
|
||||
@@ -756,19 +770,20 @@ public class UncachedKeyRing {
|
||||
|
||||
}
|
||||
|
||||
public UncachedKeyRing extractPublicKeyRing() {
|
||||
public UncachedKeyRing extractPublicKeyRing() throws IOException {
|
||||
if(!isSecret()) {
|
||||
throw new RuntimeException("Tried to extract public keyring from non-secret keyring. " +
|
||||
"This is a programming error and should never happen!");
|
||||
}
|
||||
|
||||
ArrayList<PGPPublicKey> keys = new ArrayList();
|
||||
Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream(2048);
|
||||
while (it.hasNext()) {
|
||||
keys.add(it.next());
|
||||
stream.write(it.next().getEncoded());
|
||||
}
|
||||
|
||||
return new UncachedKeyRing(new PGPPublicKeyRing(keys));
|
||||
return new UncachedKeyRing(
|
||||
new PGPPublicKeyRing(stream.toByteArray(), new JcaKeyFingerprintCalculator()));
|
||||
}
|
||||
|
||||
/** This method replaces a public key in a keyring.
|
||||
@@ -792,4 +807,20 @@ public class UncachedKeyRing {
|
||||
return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
|
||||
}
|
||||
|
||||
/** This method removes a subkey in a keyring.
|
||||
*
|
||||
* This method essentially wraps PGP*KeyRing.remove*Key, where the keyring may be of either
|
||||
* the secret or public subclass.
|
||||
*
|
||||
* @return the resulting PGPKeyRing of the same type as the input
|
||||
*/
|
||||
private static PGPKeyRing removeSubKey(PGPKeyRing ring, PGPPublicKey key) {
|
||||
if (ring instanceof PGPPublicKeyRing) {
|
||||
return PGPPublicKeyRing.removePublicKey((PGPPublicKeyRing) ring, key);
|
||||
} else {
|
||||
PGPSecretKey sKey = ((PGPSecretKeyRing) ring).getSecretKey(key.getKeyID());
|
||||
return PGPSecretKeyRing.removeSecretKey((PGPSecretKeyRing) ring, sKey);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
@@ -44,14 +42,19 @@ public class UncachedPublicKey {
|
||||
}
|
||||
|
||||
public Date getExpiryTime() {
|
||||
Date creationDate = getCreationTime();
|
||||
if (mPublicKey.getValidDays() == 0) {
|
||||
long seconds = mPublicKey.getValidSeconds();
|
||||
if (seconds > Integer.MAX_VALUE) {
|
||||
Log.e(Constants.TAG, "error, expiry time too large");
|
||||
return null;
|
||||
}
|
||||
if (seconds == 0) {
|
||||
// no expiry
|
||||
return null;
|
||||
}
|
||||
Date creationDate = getCreationTime();
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.DATE, mPublicKey.getValidDays());
|
||||
calendar.add(Calendar.SECOND, (int) seconds);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
@@ -77,26 +80,76 @@ public class UncachedPublicKey {
|
||||
return mPublicKey.getBitStrength();
|
||||
}
|
||||
|
||||
/** Returns the primary user id, as indicated by the public key's self certificates.
|
||||
*
|
||||
* This is an expensive operation, since potentially a lot of certificates (and revocations)
|
||||
* have to be checked, and even then the result is NOT guaranteed to be constant through a
|
||||
* canonicalization operation.
|
||||
*
|
||||
* Returns null if there is no primary user id (as indicated by certificates)
|
||||
*
|
||||
*/
|
||||
public String getPrimaryUserId() {
|
||||
String found = null;
|
||||
PGPSignature foundSig = null;
|
||||
for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) {
|
||||
PGPSignature revocation = null;
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignaturesForID(userId))) {
|
||||
if (sig.getHashedSubPackets() != null
|
||||
&& sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) {
|
||||
try {
|
||||
try {
|
||||
|
||||
// if this is a revocation, this is not the user id
|
||||
if (sig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
// make sure it's actually valid
|
||||
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
|
||||
if (!sig.verifyCertification(userId, mPublicKey)) {
|
||||
continue;
|
||||
}
|
||||
if (found != null && found.equals(userId)) {
|
||||
found = null;
|
||||
}
|
||||
revocation = sig;
|
||||
// this revocation may still be overridden by a newer cert
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sig.getHashedSubPackets() != null && sig.getHashedSubPackets().isPrimaryUserID()) {
|
||||
if (foundSig != null && sig.getCreationTime().before(foundSig.getCreationTime())) {
|
||||
continue;
|
||||
}
|
||||
// ignore if there is a newer revocation for this user id
|
||||
if (revocation != null && sig.getCreationTime().before(revocation.getCreationTime())) {
|
||||
continue;
|
||||
}
|
||||
// make sure it's actually valid
|
||||
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
|
||||
if (sig.verifyCertification(userId, mPublicKey)) {
|
||||
return userId;
|
||||
found = userId;
|
||||
foundSig = sig;
|
||||
// this one can't be relevant anymore at this point
|
||||
revocation = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// nothing bad happens, the key is just not considered the primary key id
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// nothing bad happens, the key is just not considered the primary key id
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns primary user id if existing. If not, return first encountered user id.
|
||||
*/
|
||||
public String getPrimaryUserIdWithFallback() {
|
||||
String userId = getPrimaryUserId();
|
||||
if (userId == null) {
|
||||
userId = (String) mPublicKey.getUserIDs().next();
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public ArrayList<String> getUnorderedUserIds() {
|
||||
@@ -186,6 +239,21 @@ public class UncachedPublicKey {
|
||||
return mPublicKey;
|
||||
}
|
||||
|
||||
public Iterator<WrappedSignature> getSignatures() {
|
||||
final Iterator<PGPSignature> it = mPublicKey.getSignatures();
|
||||
return new Iterator<WrappedSignature>() {
|
||||
public void remove() {
|
||||
it.remove();
|
||||
}
|
||||
public WrappedSignature next() {
|
||||
return new WrappedSignature(it.next());
|
||||
}
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Iterator<WrappedSignature> getSignaturesForId(String userId) {
|
||||
final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(userId);
|
||||
return new Iterator<WrappedSignature>() {
|
||||
|
||||
@@ -39,8 +39,12 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
}
|
||||
|
||||
public String getPrimaryUserId() throws PgpGeneralException {
|
||||
return (String) getRing().getPublicKey().getUserIDs().next();
|
||||
};
|
||||
return getPublicKey().getPrimaryUserId();
|
||||
}
|
||||
|
||||
public String getPrimaryUserIdWithFallback() throws PgpGeneralException {
|
||||
return getPublicKey().getPrimaryUserIdWithFallback();
|
||||
}
|
||||
|
||||
public boolean isRevoked() throws PgpGeneralException {
|
||||
// Is the master key revoked?
|
||||
@@ -101,8 +105,16 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
|
||||
abstract public IterableIterator<WrappedPublicKey> publicKeyIterator();
|
||||
|
||||
public UncachedKeyRing getUncached() {
|
||||
return new UncachedKeyRing(getRing());
|
||||
public WrappedPublicKey getPublicKey() {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey());
|
||||
}
|
||||
|
||||
public WrappedPublicKey getPublicKey(long id) {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey(id));
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return getRing().getEncoded();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
@@ -25,17 +22,20 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
|
||||
PGPPublicKeyRing getRing() {
|
||||
if(mRing == null) {
|
||||
// get first object in block
|
||||
PGPObjectFactory factory = new PGPObjectFactory(mPubKey);
|
||||
PGPKeyRing keyRing = null;
|
||||
try {
|
||||
if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
Object obj = factory.nextObject();
|
||||
if (! (obj instanceof PGPPublicKeyRing)) {
|
||||
throw new RuntimeException("Error constructing WrappedPublicKeyRing, should never happen!");
|
||||
}
|
||||
mRing = (PGPPublicKeyRing) obj;
|
||||
if (factory.nextObject() != null) {
|
||||
throw new RuntimeException("Encountered trailing data after keyring, should never happen!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e);
|
||||
throw new RuntimeException("IO Error constructing WrappedPublicKeyRing, should never happen!");
|
||||
}
|
||||
|
||||
mRing = (PGPPublicKeyRing) keyRing;
|
||||
}
|
||||
return mRing;
|
||||
}
|
||||
@@ -44,14 +44,6 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
getRing().encode(stream);
|
||||
}
|
||||
|
||||
public WrappedPublicKey getSubkey() {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey());
|
||||
}
|
||||
|
||||
public WrappedPublicKey getSubkey(long id) {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey(id));
|
||||
}
|
||||
|
||||
/** Getter that returns the subkey that should be used for signing. */
|
||||
WrappedPublicKey getEncryptionSubKey() throws PgpGeneralException {
|
||||
PGPPublicKey key = getRing().getPublicKey(getEncryptId());
|
||||
|
||||
@@ -51,14 +51,6 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
return (WrappedSecretKeyRing) mRing;
|
||||
}
|
||||
|
||||
/** Returns the wrapped PGPSecretKeyRing.
|
||||
* This function is for compatibility only, should not be used anymore and will be removed
|
||||
*/
|
||||
@Deprecated
|
||||
public PGPSecretKey getKeyExternal() {
|
||||
return mSecretKey;
|
||||
}
|
||||
|
||||
public boolean unlock(String passphrase) throws PgpGeneralException {
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
@@ -97,7 +89,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
signatureGenerator.init(signatureType, mPrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
spGen.setSignerUserID(false, mRing.getPrimaryUserId());
|
||||
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
return signatureGenerator;
|
||||
} catch(PGPException e) {
|
||||
@@ -175,7 +167,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicKeyRing.getSubkey().getPublicKey();
|
||||
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
for (String userId : new IterableIterator<String>(userIds.iterator())) {
|
||||
|
||||
@@ -41,11 +41,11 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
public WrappedSecretKey getSubKey() {
|
||||
public WrappedSecretKey getSecretKey() {
|
||||
return new WrappedSecretKey(this, mRing.getSecretKey());
|
||||
}
|
||||
|
||||
public WrappedSecretKey getSubKey(long id) {
|
||||
public WrappedSecretKey getSecretKey(long id) {
|
||||
return new WrappedSecretKey(this, mRing.getSecretKey(id));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
/** OpenKeychain wrapper around PGPSignature objects.
|
||||
@@ -55,12 +56,37 @@ public class WrappedSignature {
|
||||
return mSig.getCreationTime();
|
||||
}
|
||||
|
||||
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
|
||||
ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>();
|
||||
if (!mSig.hasSubpackets()) {
|
||||
return sigs;
|
||||
}
|
||||
try {
|
||||
PGPSignatureList list;
|
||||
list = mSig.getHashedSubPackets().getEmbeddedSignatures();
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
list = mSig.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
} catch (IOException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return mSig.getEncoded();
|
||||
}
|
||||
|
||||
public boolean isRevocation() {
|
||||
return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON);
|
||||
return mSig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION;
|
||||
}
|
||||
|
||||
public boolean isPrimaryUserId() {
|
||||
|
||||
@@ -70,6 +70,10 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrimaryUserIdWithFallback() throws PgpGeneralException {
|
||||
return getPrimaryUserId();
|
||||
}
|
||||
|
||||
public boolean isRevoked() throws PgpGeneralException {
|
||||
try {
|
||||
Object data = mProviderHelper.getGenericData(mUri,
|
||||
|
||||
@@ -199,7 +199,7 @@ public class ProviderHelper {
|
||||
byte[] blob = cursor.getBlob(3);
|
||||
if (blob != null) {
|
||||
result.put(masterKeyId,
|
||||
new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey());
|
||||
new WrappedPublicKeyRing(blob, hasAnySecret, verified).getPublicKey());
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
@@ -450,8 +450,7 @@ public class ProviderHelper {
|
||||
if (cert.verifySignature(masterKey, userId)) {
|
||||
item.trustedCerts.add(cert);
|
||||
log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD,
|
||||
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()),
|
||||
trustedKey.getPrimaryUserId()
|
||||
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId())
|
||||
);
|
||||
} else {
|
||||
log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD);
|
||||
@@ -670,7 +669,7 @@ public class ProviderHelper {
|
||||
|
||||
// If there is an old keyring, merge it
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into the old one
|
||||
publicRing = oldPublicRing.merge(publicRing, mLog, mIndent);
|
||||
@@ -706,7 +705,7 @@ public class ProviderHelper {
|
||||
// If there is a secret key, merge new data (if any) and save the key for later
|
||||
UncachedKeyRing secretRing;
|
||||
try {
|
||||
secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncached();
|
||||
secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into secret one
|
||||
secretRing = secretRing.merge(publicRing, mLog, mIndent);
|
||||
@@ -754,10 +753,10 @@ public class ProviderHelper {
|
||||
|
||||
// If there is an old secret key, merge it.
|
||||
try {
|
||||
UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new secret ring into old one
|
||||
secretRing = oldSecretRing.merge(secretRing, mLog, mIndent);
|
||||
secretRing = secretRing.merge(oldSecretRing, mLog, mIndent);
|
||||
|
||||
// If this is null, there is an error in the log so we can just return
|
||||
if (secretRing == null) {
|
||||
@@ -791,9 +790,9 @@ public class ProviderHelper {
|
||||
// Merge new data into public keyring as well, if there is any
|
||||
UncachedKeyRing publicRing;
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into secret one
|
||||
// Merge data from new secret ring into public one
|
||||
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
|
||||
@@ -53,6 +53,12 @@ import java.util.Set;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
|
||||
static final String[] KEYRING_PROJECTION =
|
||||
new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
};
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
*
|
||||
@@ -70,7 +76,7 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
for (String email : encryptionUserIds) {
|
||||
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
||||
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
|
||||
Cursor cursor = getContentResolver().query(uri, KEYRING_PROJECTION, null, null, null);
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
|
||||
@@ -349,9 +349,9 @@ public class KeychainIntentService extends IntentService
|
||||
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 10, 95, 100));
|
||||
|
||||
// cache new passphrase
|
||||
if (saveParcel.newPassphrase != null) {
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
|
||||
saveParcel.newPassphrase);
|
||||
saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback());
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
sendErrorToHandler(e);
|
||||
@@ -545,7 +545,7 @@ public class KeychainIntentService extends IntentService
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
WrappedPublicKeyRing publicRing = providerHelper.getWrappedPublicKeyRing(pubKeyId);
|
||||
WrappedSecretKeyRing secretKeyRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
|
||||
WrappedSecretKey certificationKey = secretKeyRing.getSubKey();
|
||||
WrappedSecretKey certificationKey = secretKeyRing.getSecretKey();
|
||||
if(!certificationKey.unlock(signaturePassphrase)) {
|
||||
throw new PgpGeneralException("Error extracting key (bad passphrase?)");
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/** Represent the result of an operation.
|
||||
*
|
||||
@@ -81,9 +84,6 @@ public class OperationResultParcel implements Parcelable {
|
||||
mParameters = parameters;
|
||||
mIndent = indent;
|
||||
}
|
||||
public LogEntryParcel(LogLevel level, LogType type, Object... parameters) {
|
||||
this(level, type, 0, parameters);
|
||||
}
|
||||
|
||||
public LogEntryParcel(Parcel source) {
|
||||
mLevel = LogLevel.values()[source.readInt()];
|
||||
@@ -115,6 +115,15 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogEntryParcel{" +
|
||||
"mLevel=" + mLevel +
|
||||
", mType=" + mType +
|
||||
", mParameters=" + Arrays.toString(mParameters) +
|
||||
", mIndent=" + mIndent +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
@@ -245,6 +254,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_KC_REVOKE_BAD_LOCAL (R.string.msg_kc_revoke_bad_local),
|
||||
MSG_KC_REVOKE_BAD_TIME (R.string.msg_kc_revoke_bad_time),
|
||||
MSG_KC_REVOKE_BAD_TYPE (R.string.msg_kc_revoke_bad_type),
|
||||
MSG_KC_REVOKE_BAD_TYPE_UID (R.string.msg_kc_revoke_bad_type_uid),
|
||||
MSG_KC_REVOKE_BAD (R.string.msg_kc_revoke_bad),
|
||||
MSG_KC_REVOKE_DUP (R.string.msg_kc_revoke_dup),
|
||||
MSG_KC_SUB (R.string.msg_kc_sub),
|
||||
@@ -276,6 +286,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert),
|
||||
MSG_KC_UID_REVOKE_DUP (R.string.msg_kc_uid_revoke_dup),
|
||||
MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old),
|
||||
MSG_KC_UID_REMOVE (R.string.msg_kc_uid_remove),
|
||||
|
||||
|
||||
// keyring consolidation
|
||||
@@ -285,9 +296,17 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous),
|
||||
MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),
|
||||
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
|
||||
MSG_MG_UNCHANGED (R.string.msg_mg_unchanged),
|
||||
|
||||
// secret key create
|
||||
MSG_CR_ERROR_NO_MASTER (R.string.msg_mr),
|
||||
MSG_CR (R.string.msg_cr),
|
||||
MSG_CR_ERROR_NO_MASTER (R.string.msg_cr_error_no_master),
|
||||
MSG_CR_ERROR_NO_USER_ID (R.string.msg_cr_error_no_user_id),
|
||||
MSG_CR_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify),
|
||||
MSG_CR_ERROR_KEYSIZE_512 (R.string.msg_cr_error_keysize_512),
|
||||
MSG_CR_ERROR_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo),
|
||||
MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp),
|
||||
MSG_CR_ERROR_MASTER_ELGAMAL (R.string.msg_cr_error_master_elgamal),
|
||||
|
||||
// secret key modify
|
||||
MSG_MF (R.string.msg_mr),
|
||||
@@ -295,10 +314,13 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint),
|
||||
MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),
|
||||
MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity),
|
||||
MSG_MF_ERROR_NOEXIST_PRIMARY (R.string.msg_mf_error_noexist_primary),
|
||||
MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),
|
||||
MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),
|
||||
MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),
|
||||
MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase),
|
||||
MSG_MF_PRIMARY_REPLACE_OLD (R.string.msg_mf_primary_replace_old),
|
||||
MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new),
|
||||
MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change),
|
||||
MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing),
|
||||
MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id),
|
||||
@@ -309,6 +331,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_UID_ADD (R.string.msg_mf_uid_add),
|
||||
MSG_MF_UID_PRIMARY (R.string.msg_mf_uid_primary),
|
||||
MSG_MF_UID_REVOKE (R.string.msg_mf_uid_revoke),
|
||||
MSG_MF_UID_ERROR_EMPTY (R.string.msg_mf_uid_error_empty),
|
||||
MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error),
|
||||
MSG_MF_UNLOCK (R.string.msg_mf_unlock),
|
||||
;
|
||||
@@ -340,7 +363,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mResult);
|
||||
dest.writeTypedList(mLog);
|
||||
dest.writeTypedList(mLog.toList());
|
||||
}
|
||||
|
||||
public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() {
|
||||
@@ -353,16 +376,19 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
public static class OperationLog extends ArrayList<LogEntryParcel> {
|
||||
public static class OperationLog implements Iterable<LogEntryParcel> {
|
||||
|
||||
private final List<LogEntryParcel> mParcels = new ArrayList<LogEntryParcel>();
|
||||
|
||||
/// Simple convenience method
|
||||
public void add(LogLevel level, LogType type, int indent, Object... parameters) {
|
||||
Log.d(Constants.TAG, type.toString());
|
||||
add(new OperationResultParcel.LogEntryParcel(level, type, indent, parameters));
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, parameters));
|
||||
}
|
||||
|
||||
public void add(LogLevel level, LogType type, int indent) {
|
||||
add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
|
||||
Log.d(Constants.TAG, type.toString());
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
|
||||
}
|
||||
|
||||
public LogEntryParcel getResultId() {
|
||||
@@ -374,7 +400,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
|
||||
public boolean containsWarnings() {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(iterator())) {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) {
|
||||
if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) {
|
||||
return true;
|
||||
}
|
||||
@@ -382,6 +408,22 @@ public class OperationResultParcel implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addAll(List<LogEntryParcel> parcels) {
|
||||
mParcels.addAll(parcels);
|
||||
}
|
||||
|
||||
public List<LogEntryParcel> toList() {
|
||||
return mParcels;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mParcels.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<LogEntryParcel> iterator() {
|
||||
return mParcels.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ package org.sufficientlysecure.keychain.service;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
@@ -32,11 +34,15 @@ import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -54,6 +60,8 @@ public class PassphraseCacheService extends Service {
|
||||
+ "PASSPHRASE_CACHE_ADD";
|
||||
public static final String ACTION_PASSPHRASE_CACHE_GET = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_GET";
|
||||
public static final String ACTION_PASSPHRASE_CACHE_CLEAR = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_CLEAR";
|
||||
|
||||
public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_BROADCAST";
|
||||
@@ -62,13 +70,16 @@ public class PassphraseCacheService extends Service {
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
public static final String EXTRA_USERID = "userid";
|
||||
|
||||
private static final int REQUEST_ID = 0;
|
||||
private static final long DEFAULT_TTL = 15;
|
||||
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
|
||||
private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>();
|
||||
private LongSparseArray<CachedPassphrase> mPassphraseCache = new LongSparseArray<CachedPassphrase>();
|
||||
|
||||
Context mContext;
|
||||
|
||||
@@ -81,14 +92,17 @@ public class PassphraseCacheService extends Service {
|
||||
* @param keyId
|
||||
* @param passphrase
|
||||
*/
|
||||
public static void addCachedPassphrase(Context context, long keyId, String passphrase) {
|
||||
public static void addCachedPassphrase(Context context, long keyId, String passphrase,
|
||||
String primaryUserId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + keyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
|
||||
|
||||
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassphraseCacheTtl());
|
||||
intent.putExtra(EXTRA_PASSPHRASE, passphrase);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(EXTRA_USERID, primaryUserId);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
@@ -159,11 +173,11 @@ public class PassphraseCacheService extends Service {
|
||||
// passphrase for symmetric encryption?
|
||||
if (keyId == Constants.key.symmetric) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption");
|
||||
String cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric);
|
||||
String cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric).getPassphrase();
|
||||
if (cachedPassphrase == null) {
|
||||
return null;
|
||||
}
|
||||
addCachedPassphrase(this, Constants.key.symmetric, cachedPassphrase);
|
||||
addCachedPassphrase(this, Constants.key.symmetric, cachedPassphrase, getString(R.string.passp_cache_notif_pwd));
|
||||
return cachedPassphrase;
|
||||
}
|
||||
|
||||
@@ -176,12 +190,16 @@ public class PassphraseCacheService extends Service {
|
||||
if (!key.hasPassphrase()) {
|
||||
Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
|
||||
|
||||
addCachedPassphrase(this, keyId, "");
|
||||
try {
|
||||
addCachedPassphrase(this, keyId, "", key.getPrimaryUserIdWithFallback());
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "PgpGeneralException occured");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// get cached passphrase
|
||||
String cachedPassphrase = mPassphraseCache.get(keyId);
|
||||
CachedPassphrase cachedPassphrase = mPassphraseCache.get(keyId);
|
||||
if (cachedPassphrase == null) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Passphrase not (yet) cached, returning null");
|
||||
// not really an error, just means the passphrase is not cached but not empty either
|
||||
@@ -190,8 +208,8 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
// set it again to reset the cache life cycle
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Cache passphrase again when getting it!");
|
||||
addCachedPassphrase(this, keyId, cachedPassphrase);
|
||||
return cachedPassphrase;
|
||||
addCachedPassphrase(this, keyId, cachedPassphrase.getPassphrase(), cachedPassphrase.getPrimaryUserID());
|
||||
return cachedPassphrase.getPassphrase();
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Passphrase for unknown key was requested!");
|
||||
@@ -256,14 +274,16 @@ public class PassphraseCacheService extends Service {
|
||||
if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) {
|
||||
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
|
||||
String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
|
||||
String primaryUserID = intent.getStringExtra(EXTRA_USERID);
|
||||
|
||||
Log.d(Constants.TAG,
|
||||
"PassphraseCacheService Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
|
||||
+ keyId + ", ttl: " + ttl);
|
||||
+ keyId + ", ttl: " + ttl + ", usrId: " + primaryUserID);
|
||||
|
||||
// add keyId and passphrase to memory
|
||||
mPassphraseCache.put(keyId, passphrase);
|
||||
// add keyId, passphrase and primary user id to memory
|
||||
mPassphraseCache.put(keyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
@@ -271,6 +291,9 @@ public class PassphraseCacheService extends Service {
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId));
|
||||
}
|
||||
|
||||
updateNotifications();
|
||||
|
||||
} else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
@@ -286,6 +309,17 @@ public class PassphraseCacheService extends Service {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Sending message failed", e);
|
||||
}
|
||||
} else if (ACTION_PASSPHRASE_CACHE_CLEAR.equals(intent.getAction())) {
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
// Stop all ttl alarms
|
||||
for(int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
|
||||
}
|
||||
|
||||
mPassphraseCache.clear();
|
||||
|
||||
updateNotifications();
|
||||
} else {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Intent or Intent Action not supported!");
|
||||
}
|
||||
@@ -311,6 +345,74 @@ public class PassphraseCacheService extends Service {
|
||||
Log.d(Constants.TAG, "PassphraseCacheServic No passphrases remaining in memory, stopping service!");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
updateNotifications();
|
||||
}
|
||||
|
||||
private void updateNotifications() {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
if(mPassphraseCache.size() > 0) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(String.format(getString(R.string.passp_cache_notif_n_keys), mPassphraseCache.size()));
|
||||
|
||||
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
|
||||
|
||||
inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys));
|
||||
|
||||
// Moves events into the big view
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID());
|
||||
}
|
||||
|
||||
// Moves the big view style object into the notification object.
|
||||
builder.setStyle(inboxStyle);
|
||||
|
||||
// Add purging action
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
builder.addAction(
|
||||
R.drawable.abc_ic_clear_normal,
|
||||
getString(R.string.passp_cache_notif_clear),
|
||||
PendingIntent.getService(
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
);
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
||||
} else { // Fallback, since expandable notifications weren't available back then
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys, mPassphraseCache.size())))
|
||||
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
||||
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
|
||||
builder.setContentIntent(
|
||||
PendingIntent.getService(
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
);
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
} else {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -341,4 +443,27 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
private final IBinder mBinder = new PassphraseCacheBinder();
|
||||
|
||||
}
|
||||
public class CachedPassphrase {
|
||||
private String primaryUserID;
|
||||
private String passphrase;
|
||||
|
||||
public CachedPassphrase(String passphrase, String primaryUserID) {
|
||||
setPassphrase(passphrase);
|
||||
setPrimaryUserID(primaryUserID);
|
||||
}
|
||||
|
||||
public String getPrimaryUserID() {
|
||||
return primaryUserID;
|
||||
}
|
||||
public String getPassphrase() {
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
public void setPrimaryUserID(String primaryUserID) {
|
||||
this.primaryUserID = primaryUserID;
|
||||
}
|
||||
public void setPassphrase(String passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,23 +27,19 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
// the key fingerprint, for safety. MUST be null for a new key.
|
||||
public byte[] mFingerprint;
|
||||
|
||||
public String newPassphrase;
|
||||
public String mNewPassphrase;
|
||||
|
||||
public ArrayList<String> addUserIds;
|
||||
public ArrayList<SubkeyAdd> addSubKeys;
|
||||
public ArrayList<String> mAddUserIds;
|
||||
public ArrayList<SubkeyAdd> mAddSubKeys;
|
||||
|
||||
public ArrayList<SubkeyChange> changeSubKeys;
|
||||
public String changePrimaryUserId;
|
||||
public ArrayList<SubkeyChange> mChangeSubKeys;
|
||||
public String mChangePrimaryUserId;
|
||||
|
||||
public ArrayList<String> revokeUserIds;
|
||||
public ArrayList<Long> revokeSubKeys;
|
||||
public ArrayList<String> mRevokeUserIds;
|
||||
public ArrayList<Long> mRevokeSubKeys;
|
||||
|
||||
public SaveKeyringParcel() {
|
||||
addUserIds = new ArrayList<String>();
|
||||
addSubKeys = new ArrayList<SubkeyAdd>();
|
||||
changeSubKeys = new ArrayList<SubkeyChange>();
|
||||
revokeUserIds = new ArrayList<String>();
|
||||
revokeSubKeys = new ArrayList<Long>();
|
||||
reset();
|
||||
}
|
||||
|
||||
public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
|
||||
@@ -52,6 +48,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mFingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mNewPassphrase = null;
|
||||
mAddUserIds = new ArrayList<String>();
|
||||
mAddSubKeys = new ArrayList<SubkeyAdd>();
|
||||
mChangePrimaryUserId = null;
|
||||
mChangeSubKeys = new ArrayList<SubkeyChange>();
|
||||
mRevokeUserIds = new ArrayList<String>();
|
||||
mRevokeSubKeys = new ArrayList<Long>();
|
||||
}
|
||||
|
||||
// performance gain for using Parcelable here would probably be negligible,
|
||||
// use Serializable instead.
|
||||
public static class SubkeyAdd implements Serializable {
|
||||
@@ -70,6 +76,7 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
public static class SubkeyChange implements Serializable {
|
||||
public long mKeyId;
|
||||
public Integer mFlags;
|
||||
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
|
||||
public Long mExpiry;
|
||||
public SubkeyChange(long keyId, Integer flags, Long expiry) {
|
||||
mKeyId = keyId;
|
||||
@@ -82,16 +89,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
mFingerprint = source.createByteArray();
|
||||
|
||||
newPassphrase = source.readString();
|
||||
mNewPassphrase = source.readString();
|
||||
|
||||
addUserIds = source.createStringArrayList();
|
||||
addSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
|
||||
mAddUserIds = source.createStringArrayList();
|
||||
mAddSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
|
||||
|
||||
changeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
|
||||
changePrimaryUserId = source.readString();
|
||||
mChangeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
|
||||
mChangePrimaryUserId = source.readString();
|
||||
|
||||
revokeUserIds = source.createStringArrayList();
|
||||
revokeSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
mRevokeUserIds = source.createStringArrayList();
|
||||
mRevokeSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,16 +109,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
}
|
||||
destination.writeByteArray(mFingerprint);
|
||||
|
||||
destination.writeString(newPassphrase);
|
||||
destination.writeString(mNewPassphrase);
|
||||
|
||||
destination.writeStringList(addUserIds);
|
||||
destination.writeSerializable(addSubKeys);
|
||||
destination.writeStringList(mAddUserIds);
|
||||
destination.writeSerializable(mAddSubKeys);
|
||||
|
||||
destination.writeSerializable(changeSubKeys);
|
||||
destination.writeString(changePrimaryUserId);
|
||||
destination.writeSerializable(mChangeSubKeys);
|
||||
destination.writeString(mChangePrimaryUserId);
|
||||
|
||||
destination.writeStringList(revokeUserIds);
|
||||
destination.writeSerializable(revokeSubKeys);
|
||||
destination.writeStringList(mRevokeUserIds);
|
||||
destination.writeSerializable(mRevokeSubKeys);
|
||||
}
|
||||
|
||||
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.NullProgressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Helper for tests of the Keyring import in ProviderHelper.
|
||||
*/
|
||||
public class KeyringTestingHelper {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public KeyringTestingHelper(Context robolectricContext) {
|
||||
this.context = robolectricContext;
|
||||
}
|
||||
|
||||
public boolean addKeyring(Collection<String> blobFiles) throws Exception {
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(context);
|
||||
|
||||
byte[] data = TestDataUtil.readAllFully(blobFiles);
|
||||
UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
|
||||
long masterKeyId = ring.getMasterKeyId();
|
||||
|
||||
// Should throw an exception; key is not yet saved
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId);
|
||||
|
||||
OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
|
||||
|
||||
boolean saveSuccess = saveKeyringResult.success();
|
||||
|
||||
// Now re-retrieve the saved key. Should not throw an exception.
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
|
||||
// A different ID should still fail
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
|
||||
|
||||
return saveSuccess;
|
||||
}
|
||||
|
||||
|
||||
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
|
||||
try {
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
throw new AssertionError("Was expecting the previous call to fail!");
|
||||
} catch (ProviderHelper.NotFoundException expectedException) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* For functional tests of PgpDecryptVerify
|
||||
*/
|
||||
public class PgpVerifyTestingHelper {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public PgpVerifyTestingHelper(Context robolectricContext) {
|
||||
this.context = robolectricContext;
|
||||
}
|
||||
|
||||
public int doTestFile(String testFileName) throws Exception {
|
||||
ProviderHelper providerHelper = new ProviderHelperStub(context);
|
||||
|
||||
PgpDecryptVerify.PassphraseCache passphraseCache = new PgpDecryptVerify.PassphraseCache() {
|
||||
public String getCachedPassphrase(long masterKeyId) {
|
||||
return "I am a passphrase";
|
||||
}
|
||||
};
|
||||
|
||||
byte[] sampleInputBytes = TestDataUtil.readFully(getClass().getResourceAsStream(testFileName));
|
||||
|
||||
InputStream sampleInput = new ByteArrayInputStream(sampleInputBytes);
|
||||
|
||||
InputData data = new InputData(sampleInput, sampleInputBytes.length);
|
||||
OutputStream outStream = new ByteArrayOutputStream();
|
||||
|
||||
PgpDecryptVerify verify = new PgpDecryptVerify.Builder(providerHelper, passphraseCache, data, outStream).build();
|
||||
PgpDecryptVerifyResult result = verify.execute();
|
||||
|
||||
return result.getSignatureResult().getStatus();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
/**
|
||||
* Created by art on 21/06/14.
|
||||
*/
|
||||
class ProviderHelperStub extends ProviderHelper {
|
||||
public ProviderHelperStub(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
|
||||
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
|
||||
return new WrappedPublicKeyRing(data, false, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Misc support functions. Would just use Guava / Apache Commons but
|
||||
* avoiding extra dependencies.
|
||||
*/
|
||||
public class TestDataUtil {
|
||||
public static byte[] readFully(InputStream input) {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
appendToOutput(input, output);
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private static void appendToOutput(InputStream input, ByteArrayOutputStream output) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
try {
|
||||
while ((bytesRead = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readAllFully(Collection<String> inputResources) {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
for (String inputResource : inputResources) {
|
||||
appendToOutput(getResourceAsStream(inputResource), output);
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public static InputStream getResourceAsStream(String resourceName) {
|
||||
return TestDataUtil.class.getResourceAsStream(resourceName);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Test support classes.
|
||||
* This is only in main code because of gradle-Android Studio-robolectric issues. Having
|
||||
* classes in main code means IDE autocomplete, class detection, etc., all works.
|
||||
* TODO Move into test package when possible
|
||||
*/
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class CreateKeyActivity extends ActionBarActivity {
|
||||
|
||||
AutoCompleteTextView mNameEdit;
|
||||
AutoCompleteTextView mEmailEdit;
|
||||
EditText mPassphraseEdit;
|
||||
Button mCreateButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.create_key_activity);
|
||||
|
||||
mNameEdit = (AutoCompleteTextView) findViewById(R.id.name);
|
||||
mEmailEdit = (AutoCompleteTextView) findViewById(R.id.email);
|
||||
mPassphraseEdit = (EditText) findViewById(R.id.passphrase);
|
||||
mCreateButton = (Button) findViewById(R.id.create_key_button);
|
||||
|
||||
mEmailEdit.setThreshold(1); // Start working from first character
|
||||
mEmailEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(this, android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserEmails(this)
|
||||
)
|
||||
);
|
||||
mEmailEdit.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String email = editable.toString();
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
|
||||
if (emailMatcher.matches()) {
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_ok, 0);
|
||||
} else {
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mNameEdit.setThreshold(1); // Start working from first character
|
||||
mNameEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(this, android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserNames(this)
|
||||
)
|
||||
);
|
||||
|
||||
mCreateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createKeyCheck();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void createKeyCheck() {
|
||||
if (isEditTextNotEmpty(this, mNameEdit)
|
||||
&& isEditTextNotEmpty(this, mEmailEdit)
|
||||
&& isEditTextNotEmpty(this, mPassphraseEdit)) {
|
||||
createKey();
|
||||
}
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
CreateKeyActivity.this.setResult(RESULT_OK);
|
||||
CreateKeyActivity.this.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
|
||||
String userId = mNameEdit.getText().toString() + " <" + mEmailEdit.getText().toString() + ">";
|
||||
parcel.mAddUserIds.add(userId);
|
||||
parcel.mNewPassphrase = mPassphraseEdit.getText().toString();
|
||||
|
||||
// get selected key entries
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
||||
* set and the EditText gets the focus.
|
||||
*
|
||||
* @param context
|
||||
* @param editText
|
||||
* @return true if EditText is not empty
|
||||
*/
|
||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
||||
boolean output = true;
|
||||
if (editText.getText().toString().length() == 0) {
|
||||
editText.setError("empty!");
|
||||
editText.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText.setError(null);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -54,12 +54,15 @@ import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ChangeExpiryDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
public class EditKeyFragment extends LoaderFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
@@ -214,10 +217,18 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mUserIdsAddedData);
|
||||
mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter);
|
||||
|
||||
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0);
|
||||
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0, mSaveKeyringParcel);
|
||||
mSubkeysList.setAdapter(mSubkeysAdapter);
|
||||
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.addSubKeys);
|
||||
mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
long keyId = mSubkeysAdapter.getKeyId(position);
|
||||
editSubkey(keyId);
|
||||
}
|
||||
});
|
||||
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys);
|
||||
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
@@ -287,7 +298,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
Bundle data = message.getData();
|
||||
|
||||
// cache new returned passphrase!
|
||||
mSaveKeyringParcel.newPassphrase = data
|
||||
mSaveKeyringParcel.mNewPassphrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
}
|
||||
}
|
||||
@@ -309,19 +320,19 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
switch (message.what) {
|
||||
case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
|
||||
mSaveKeyringParcel.changePrimaryUserId = null;
|
||||
if (mSaveKeyringParcel.mChangePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) {
|
||||
mSaveKeyringParcel.mChangePrimaryUserId = null;
|
||||
} else {
|
||||
mSaveKeyringParcel.changePrimaryUserId = userId;
|
||||
mSaveKeyringParcel.mChangePrimaryUserId = userId;
|
||||
}
|
||||
break;
|
||||
case EditUserIdDialogFragment.MESSAGE_REVOKE:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.revokeUserIds.contains(userId)) {
|
||||
mSaveKeyringParcel.revokeUserIds.remove(userId);
|
||||
if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) {
|
||||
mSaveKeyringParcel.mRevokeUserIds.remove(userId);
|
||||
} else {
|
||||
mSaveKeyringParcel.revokeUserIds.add(userId);
|
||||
mSaveKeyringParcel.mRevokeUserIds.add(userId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -342,6 +353,72 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
});
|
||||
}
|
||||
|
||||
private void editSubkey(final long keyId) {
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY:
|
||||
editSubkeyExpiry(keyId);
|
||||
break;
|
||||
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) {
|
||||
mSaveKeyringParcel.mRevokeSubKeys.remove(keyId);
|
||||
} else {
|
||||
mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
final Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||
public void run() {
|
||||
EditSubkeyDialogFragment dialogFragment =
|
||||
EditSubkeyDialogFragment.newInstance(messenger);
|
||||
|
||||
dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyDialog");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void editSubkeyExpiry(final long keyId) {
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case ChangeExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE:
|
||||
// toggle
|
||||
// if (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
// && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
|
||||
// mSaveKeyringParcel.changePrimaryUserId = null;
|
||||
// } else {
|
||||
// mSaveKeyringParcel.changePrimaryUserId = userId;
|
||||
// }
|
||||
break;
|
||||
}
|
||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
final Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||
public void run() {
|
||||
ChangeExpiryDialogFragment dialogFragment =
|
||||
ChangeExpiryDialogFragment.newInstance(messenger, new Date(), new Date());
|
||||
|
||||
dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addUserId() {
|
||||
mUserIdsAddedAdapter.add(new UserIdsAddedAdapter.UserIdModel());
|
||||
}
|
||||
@@ -373,10 +450,11 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
private void save(String passphrase) {
|
||||
Log.d(Constants.TAG, "add userids to parcel: " + mUserIdsAddedAdapter.getDataAsStringList());
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.newPassphrase: " + mSaveKeyringParcel.newPassphrase);
|
||||
mSaveKeyringParcel.mAddUserIds = mUserIdsAddedAdapter.getDataAsStringList();
|
||||
|
||||
mSaveKeyringParcel.addUserIds = mUserIdsAddedAdapter.getDataAsStringList();
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mAddUserIds: " + mSaveKeyringParcel.mAddUserIds);
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase);
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mRevokeUserIds: " + mSaveKeyringParcel.mRevokeUserIds);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
@@ -432,4 +510,4 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public class EncryptAsymmetricFragment extends Fragment {
|
||||
String[] userId;
|
||||
try {
|
||||
userId = mProviderHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserId();
|
||||
KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserIdWithFallback();
|
||||
} catch (PgpGeneralException e) {
|
||||
userId = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
|
||||
public class FirstTimeActivity extends ActionBarActivity {
|
||||
|
||||
Button mCreateKey;
|
||||
Button mImportKey;
|
||||
Button mSkipSetup;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.first_time_activity);
|
||||
|
||||
mCreateKey = (Button) findViewById(R.id.first_time_create_key);
|
||||
mImportKey = (Button) findViewById(R.id.first_time_import_key);
|
||||
mSkipSetup = (Button) findViewById(R.id.first_time_cancel);
|
||||
|
||||
mSkipSetup.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finishSetup();
|
||||
}
|
||||
});
|
||||
|
||||
mImportKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FirstTimeActivity.this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, 1);
|
||||
}
|
||||
});
|
||||
|
||||
mCreateKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FirstTimeActivity.this, CreateKeyActivity.class);
|
||||
startActivityForResult(intent, 1);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
finishSetup();
|
||||
}
|
||||
}
|
||||
|
||||
private void finishSetup() {
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -17,25 +17,17 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
@@ -49,6 +41,14 @@ public class KeyListActivity extends DrawerActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// if this is the first time show first time activity
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.isFirstTime()) {
|
||||
startActivity(new Intent(this, FirstTimeActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
setContentView(R.layout.key_list_activity);
|
||||
@@ -62,9 +62,10 @@ public class KeyListActivity extends DrawerActivity {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.key_list, menu);
|
||||
|
||||
if(Constants.DEBUG) {
|
||||
if (Constants.DEBUG) {
|
||||
menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);
|
||||
menu.findItem(R.id.menu_key_list_debug_write).setVisible(true);
|
||||
menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -81,10 +82,6 @@ public class KeyListActivity extends DrawerActivity {
|
||||
createKey();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_create_expert:
|
||||
createKeyExpert();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_export:
|
||||
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
return true;
|
||||
@@ -94,7 +91,7 @@ public class KeyListActivity extends DrawerActivity {
|
||||
KeychainDatabase.debugRead(this);
|
||||
Notify.showNotify(this, "Restored Notify.Style backup", Notify.Style.INFO);
|
||||
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
@@ -110,6 +107,12 @@ public class KeyListActivity extends DrawerActivity {
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_first_time:
|
||||
Intent intent = new Intent(this, FirstTimeActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -121,50 +124,8 @@ public class KeyListActivity extends DrawerActivity {
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(this, WizardActivity.class);
|
||||
// intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
|
||||
// intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
// intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
|
||||
startActivityForResult(intent, 0);
|
||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void createKeyExpert() {
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
Bundle data = message.getData();
|
||||
// OtherHelper.logDebugBundle(data, "message reply");
|
||||
}
|
||||
};
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.addUserIds.add("swagerinho");
|
||||
parcel.newPassphrase = "swag";
|
||||
|
||||
// get selected key entries
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class LogDisplayFragment extends ListFragment implements OnTouchListener {
|
||||
|
||||
@@ -135,7 +136,7 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener
|
||||
private LayoutInflater mInflater;
|
||||
private int dipFactor;
|
||||
|
||||
public LogAdapter(Context context, ArrayList<LogEntryParcel> log, LogLevel level) {
|
||||
public LogAdapter(Context context, OperationResultParcel.OperationLog log, LogLevel level) {
|
||||
super(context, R.layout.log_display_item);
|
||||
mInflater = LayoutInflater.from(getContext());
|
||||
dipFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||
|
||||
@@ -121,6 +121,9 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
initializeForceV3Signatures(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||
|
||||
initializeConcealPgpApplication(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION));
|
||||
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Load the legacy preferences headers
|
||||
addPreferencesFromResource(R.xml.preference_headers_legacy);
|
||||
@@ -264,6 +267,9 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
|
||||
initializeForceV3Signatures(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||
|
||||
initializeConcealPgpApplication(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,4 +402,15 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeConcealPgpApplication(final CheckBoxPreference mConcealPgpApplication) {
|
||||
mConcealPgpApplication.setChecked(sPreferences.getConcealPgpApplication());
|
||||
mConcealPgpApplication.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mConcealPgpApplication.setChecked((Boolean) newValue);
|
||||
sPreferences.setConcealPgpApplication((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,8 +149,8 @@ public class ViewCertActivity extends ActionBarActivity
|
||||
providerHelper.getWrappedPublicKeyRing(sig.getKeyId());
|
||||
|
||||
try {
|
||||
sig.init(signerRing.getSubkey());
|
||||
if (sig.verifySignature(signeeRing.getSubkey(), signeeUid)) {
|
||||
sig.init(signerRing.getPublicKey());
|
||||
if (sig.verifySignature(signeeRing.getPublicKey(), signeeUid)) {
|
||||
mStatus.setText(R.string.cert_verify_ok);
|
||||
mStatus.setTextColor(getResources().getColor(R.color.result_green));
|
||||
} else {
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
|
||||
public class WizardActivity extends ActionBarActivity {
|
||||
|
||||
private State mCurrentState;
|
||||
|
||||
// values for mCurrentScreen
|
||||
private enum State {
|
||||
START, CREATE_KEY, IMPORT_KEY, K9
|
||||
}
|
||||
|
||||
public static final int REQUEST_CODE_IMPORT = 0x00007703;
|
||||
|
||||
Button mBackButton;
|
||||
Button mNextButton;
|
||||
StartFragment mStartFragment;
|
||||
CreateKeyFragment mCreateKeyFragment;
|
||||
K9Fragment mK9Fragment;
|
||||
|
||||
private static final String K9_PACKAGE = "com.fsck.k9";
|
||||
// private static final String K9_MARKET_INTENT_URI_BASE = "market://details?id=%s";
|
||||
// private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse(
|
||||
// String.format(K9_MARKET_INTENT_URI_BASE, K9_PACKAGE)));
|
||||
private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/k9mail/k-9/releases/tag/4.904"));
|
||||
|
||||
LinearLayout mProgressLayout;
|
||||
View mProgressLine;
|
||||
ProgressBar mProgressBar;
|
||||
ImageView mProgressImage;
|
||||
TextView mProgressText;
|
||||
|
||||
/**
|
||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
||||
* set and the EditText gets the focus.
|
||||
*
|
||||
* @param context
|
||||
* @param editText
|
||||
* @return true if EditText is not empty
|
||||
*/
|
||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
||||
boolean output = true;
|
||||
if (editText.getText().toString().length() == 0) {
|
||||
editText.setError("empty!");
|
||||
editText.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText.setError(null);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static class StartFragment extends Fragment {
|
||||
public static StartFragment newInstance() {
|
||||
StartFragment myFragment = new StartFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
myFragment.setArguments(args);
|
||||
|
||||
return myFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.wizard_start_fragment,
|
||||
container, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CreateKeyFragment extends Fragment {
|
||||
public static CreateKeyFragment newInstance() {
|
||||
CreateKeyFragment myFragment = new CreateKeyFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
myFragment.setArguments(args);
|
||||
|
||||
return myFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.wizard_create_key_fragment,
|
||||
container, false);
|
||||
|
||||
final AutoCompleteTextView emailView = (AutoCompleteTextView) view.findViewById(R.id.email);
|
||||
emailView.setThreshold(1); // Start working from first character
|
||||
emailView.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserEmails(getActivity())
|
||||
)
|
||||
);
|
||||
emailView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String email = editable.toString();
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
|
||||
if (emailMatcher.matches()) {
|
||||
emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_ok, 0);
|
||||
} else {
|
||||
emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
final AutoCompleteTextView nameView = (AutoCompleteTextView) view.findViewById(R.id.name);
|
||||
nameView.setThreshold(1); // Start working from first character
|
||||
nameView.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserNames(getActivity())
|
||||
)
|
||||
);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
public static class K9Fragment extends Fragment {
|
||||
public static K9Fragment newInstance() {
|
||||
K9Fragment myFragment = new K9Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
myFragment.setArguments(args);
|
||||
|
||||
return myFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.wizard_k9_fragment,
|
||||
container, false);
|
||||
|
||||
HtmlTextView text = (HtmlTextView) v
|
||||
.findViewById(R.id.wizard_k9_text);
|
||||
text.setHtmlFromString("Install K9. It's good for you! Here is a screenhot how to enable OK in K9: (TODO)", true);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads new fragment
|
||||
*
|
||||
* @param fragment
|
||||
*/
|
||||
private void loadFragment(Fragment fragment) {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager
|
||||
.beginTransaction();
|
||||
fragmentTransaction.replace(R.id.wizard_container,
|
||||
fragment);
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate View and initialize fragments for this Activity
|
||||
*/
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.wizard_activity);
|
||||
mBackButton = (Button) findViewById(R.id.wizard_back);
|
||||
mNextButton = (Button) findViewById(R.id.wizard_next);
|
||||
|
||||
// progress layout
|
||||
mProgressLayout = (LinearLayout) findViewById(R.id.wizard_progress);
|
||||
mProgressLine = findViewById(R.id.wizard_progress_line);
|
||||
mProgressBar = (ProgressBar) findViewById(R.id.wizard_progress_progressbar);
|
||||
mProgressImage = (ImageView) findViewById(R.id.wizard_progress_image);
|
||||
mProgressText = (TextView) findViewById(R.id.wizard_progress_text);
|
||||
|
||||
changeToState(State.START);
|
||||
}
|
||||
|
||||
private enum ProgressState {
|
||||
WORKING, ENABLED, DISABLED, ERROR
|
||||
}
|
||||
|
||||
private void showProgress(ProgressState state, String text) {
|
||||
switch (state) {
|
||||
case WORKING:
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressImage.setVisibility(View.GONE);
|
||||
break;
|
||||
case ENABLED:
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressImage.setVisibility(View.VISIBLE);
|
||||
// mProgressImage.setImageDrawable(getResources().getDrawable(
|
||||
// R.drawable.status_enabled));
|
||||
break;
|
||||
case DISABLED:
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressImage.setVisibility(View.VISIBLE);
|
||||
// mProgressImage.setImageDrawable(getResources().getDrawable(
|
||||
// R.drawable.status_disabled));
|
||||
break;
|
||||
case ERROR:
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressImage.setVisibility(View.VISIBLE);
|
||||
// mProgressImage.setImageDrawable(getResources().getDrawable(
|
||||
// R.drawable.status_fail));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mProgressText.setText(text);
|
||||
|
||||
mProgressLine.setVisibility(View.VISIBLE);
|
||||
mProgressLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideProgress() {
|
||||
mProgressLine.setVisibility(View.GONE);
|
||||
mProgressLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void nextOnClick(View view) {
|
||||
// close keyboard
|
||||
if (getCurrentFocus() != null) {
|
||||
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.hideSoftInputFromWindow(getCurrentFocus()
|
||||
.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
}
|
||||
|
||||
switch (mCurrentState) {
|
||||
case START: {
|
||||
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_start_radio_group);
|
||||
int selectedId = radioGroup.getCheckedRadioButtonId();
|
||||
switch (selectedId) {
|
||||
case R.id.wizard_start_new_key: {
|
||||
changeToState(State.CREATE_KEY);
|
||||
break;
|
||||
}
|
||||
case R.id.wizard_start_import: {
|
||||
changeToState(State.IMPORT_KEY);
|
||||
break;
|
||||
}
|
||||
case R.id.wizard_start_skip: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mBackButton.setText(R.string.btn_back);
|
||||
break;
|
||||
}
|
||||
case CREATE_KEY:
|
||||
EditText nameEdit = (EditText) findViewById(R.id.name);
|
||||
EditText emailEdit = (EditText) findViewById(R.id.email);
|
||||
EditText passphraseEdit = (EditText) findViewById(R.id.passphrase);
|
||||
|
||||
if (isEditTextNotEmpty(this, nameEdit)
|
||||
&& isEditTextNotEmpty(this, emailEdit)
|
||||
&& isEditTextNotEmpty(this, passphraseEdit)) {
|
||||
|
||||
// SaveKeyringParcel newKey = new SaveKeyringParcel();
|
||||
// newKey.addUserIds.add(nameEdit.getText().toString() + " <"
|
||||
// + emailEdit.getText().toString() + ">");
|
||||
|
||||
|
||||
AsyncTask<String, Boolean, Boolean> generateTask = new AsyncTask<String, Boolean, Boolean>() {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
|
||||
showProgress(ProgressState.WORKING, "generating key...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (result) {
|
||||
showProgress(ProgressState.ENABLED, "key generated successfully!");
|
||||
|
||||
changeToState(State.K9);
|
||||
} else {
|
||||
showProgress(ProgressState.ERROR, "error in key gen");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
generateTask.execute("");
|
||||
}
|
||||
break;
|
||||
case K9: {
|
||||
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_k9_radio_group);
|
||||
int selectedId = radioGroup.getCheckedRadioButtonId();
|
||||
switch (selectedId) {
|
||||
case R.id.wizard_k9_install: {
|
||||
try {
|
||||
startActivity(K9_MARKET_INTENT);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Activity not found for: " + K9_MARKET_INTENT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R.id.wizard_k9_skip: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_IMPORT: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// imported now...
|
||||
changeToState(State.K9);
|
||||
} else {
|
||||
// back to start
|
||||
changeToState(State.START);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void backOnClick(View view) {
|
||||
switch (mCurrentState) {
|
||||
case START:
|
||||
finish();
|
||||
break;
|
||||
case CREATE_KEY:
|
||||
changeToState(State.START);
|
||||
break;
|
||||
case IMPORT_KEY:
|
||||
changeToState(State.START);
|
||||
break;
|
||||
default:
|
||||
changeToState(State.START);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void changeToState(State state) {
|
||||
switch (state) {
|
||||
case START: {
|
||||
mCurrentState = State.START;
|
||||
mStartFragment = StartFragment.newInstance();
|
||||
loadFragment(mStartFragment);
|
||||
mBackButton.setText(android.R.string.cancel);
|
||||
mNextButton.setText(R.string.btn_next);
|
||||
break;
|
||||
}
|
||||
case CREATE_KEY: {
|
||||
mCurrentState = State.CREATE_KEY;
|
||||
mCreateKeyFragment = CreateKeyFragment.newInstance();
|
||||
loadFragment(mCreateKeyFragment);
|
||||
break;
|
||||
}
|
||||
case IMPORT_KEY: {
|
||||
mCurrentState = State.IMPORT_KEY;
|
||||
Intent intent = new Intent(this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, REQUEST_CODE_IMPORT);
|
||||
break;
|
||||
}
|
||||
case K9: {
|
||||
mCurrentState = State.K9;
|
||||
mBackButton.setEnabled(false); // don't go back to import/create key
|
||||
mK9Fragment = K9Fragment.newInstance();
|
||||
loadFragment(mK9Fragment);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,14 +32,15 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class SubkeysAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
private SaveKeyringParcel mSaveKeyringParcel;
|
||||
|
||||
private boolean hasAnySecret;
|
||||
|
||||
private ColorStateList mDefaultTextColor;
|
||||
|
||||
public static final String[] SUBKEYS_PROJECTION = new String[]{
|
||||
@@ -71,10 +72,21 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
private static final int INDEX_EXPIRY = 11;
|
||||
private static final int INDEX_FINGERPRINT = 12;
|
||||
|
||||
public SubkeysAdapter(Context context, Cursor c, int flags) {
|
||||
public SubkeysAdapter(Context context, Cursor c, int flags,
|
||||
SaveKeyringParcel saveKeyringParcel) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mSaveKeyringParcel = saveKeyringParcel;
|
||||
}
|
||||
|
||||
public SubkeysAdapter(Context context, Cursor c, int flags) {
|
||||
this(context, c, flags, null);
|
||||
}
|
||||
|
||||
public long getKeyId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(INDEX_KEY_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,79 +106,94 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
TextView keyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
|
||||
ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||
ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
||||
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
ImageView revokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
|
||||
TextView vKeyId = (TextView) view.findViewById(R.id.keyId);
|
||||
TextView vKeyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
TextView vKeyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
|
||||
ImageView vMasterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||
ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
||||
ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
ImageView vSignIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
ImageView vRevokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
|
||||
ImageView vEditImage = (ImageView) view.findViewById(R.id.edit_image);
|
||||
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(INDEX_KEY_ID));
|
||||
long keyId = cursor.getLong(INDEX_KEY_ID);
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
||||
context,
|
||||
cursor.getInt(INDEX_ALGORITHM),
|
||||
cursor.getInt(INDEX_KEY_SIZE)
|
||||
);
|
||||
|
||||
keyId.setText(keyIdStr);
|
||||
vKeyId.setText(keyIdStr);
|
||||
// may be set with additional "stripped" later on
|
||||
if (hasAnySecret && cursor.getInt(INDEX_HAS_SECRET) == 0) {
|
||||
keyDetails.setText(algorithmStr + ", " +
|
||||
vKeyDetails.setText(algorithmStr + ", " +
|
||||
context.getString(R.string.key_stripped));
|
||||
} else {
|
||||
keyDetails.setText(algorithmStr);
|
||||
vKeyDetails.setText(algorithmStr);
|
||||
}
|
||||
|
||||
// Set icons according to properties
|
||||
masterKeyIcon.setVisibility(cursor.getInt(INDEX_RANK) == 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
certifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE);
|
||||
encryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE);
|
||||
signIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE);
|
||||
vMasterKeyIcon.setVisibility(cursor.getInt(INDEX_RANK) == 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
vCertifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE);
|
||||
vEncryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE);
|
||||
vSignIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean valid = true;
|
||||
if (cursor.getInt(INDEX_IS_REVOKED) > 0) {
|
||||
revokedKeyIcon.setVisibility(View.VISIBLE);
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
|
||||
valid = false;
|
||||
// for edit key
|
||||
if (mSaveKeyringParcel != null) {
|
||||
boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId));
|
||||
|
||||
if (revokeThisSubkey) {
|
||||
if (!isRevoked) {
|
||||
isRevoked = true;
|
||||
}
|
||||
}
|
||||
|
||||
vEditImage.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
keyId.setTextColor(mDefaultTextColor);
|
||||
keyDetails.setTextColor(mDefaultTextColor);
|
||||
keyExpiry.setTextColor(mDefaultTextColor);
|
||||
|
||||
revokedKeyIcon.setVisibility(View.GONE);
|
||||
vEditImage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (isRevoked) {
|
||||
vRevokedKeyIcon.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vKeyId.setTextColor(mDefaultTextColor);
|
||||
vKeyDetails.setTextColor(mDefaultTextColor);
|
||||
vKeyExpiry.setTextColor(mDefaultTextColor);
|
||||
|
||||
vRevokedKeyIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean isExpired;
|
||||
if (!cursor.isNull(INDEX_EXPIRY)) {
|
||||
Date expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
|
||||
isExpired = expiryDate.before(new Date());
|
||||
|
||||
valid = valid && expiryDate.after(new Date());
|
||||
keyExpiry.setText(
|
||||
context.getString(R.string.label_expiry) + ": " +
|
||||
DateFormat.getDateFormat(context).format(expiryDate)
|
||||
);
|
||||
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": "
|
||||
+ DateFormat.getDateFormat(context).format(expiryDate));
|
||||
} else {
|
||||
keyExpiry.setText(
|
||||
context.getString(R.string.label_expiry) + ": " +
|
||||
context.getString(R.string.none)
|
||||
);
|
||||
isExpired = false;
|
||||
|
||||
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": " + context.getString(R.string.none));
|
||||
}
|
||||
|
||||
// if key is expired or revoked, strike through text
|
||||
if (!valid) {
|
||||
keyId.setText(OtherHelper.strikeOutText(keyId.getText()));
|
||||
keyDetails.setText(OtherHelper.strikeOutText(keyDetails.getText()));
|
||||
keyExpiry.setText(OtherHelper.strikeOutText(keyExpiry.getText()));
|
||||
boolean isInvalid = isRevoked || isExpired;
|
||||
if (isInvalid) {
|
||||
vKeyId.setText(OtherHelper.strikeOutText(vKeyId.getText()));
|
||||
vKeyDetails.setText(OtherHelper.strikeOutText(vKeyDetails.getText()));
|
||||
vKeyExpiry.setText(OtherHelper.strikeOutText(vKeyExpiry.getText()));
|
||||
}
|
||||
keyId.setEnabled(valid);
|
||||
keyDetails.setEnabled(valid);
|
||||
keyExpiry.setEnabled(valid);
|
||||
vKeyId.setEnabled(!isInvalid);
|
||||
vKeyDetails.setEnabled(!isInvalid);
|
||||
vKeyExpiry.setEnabled(!isInvalid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = mInflater.inflate(R.layout.view_key_keys_item, null);
|
||||
View view = mInflater.inflate(R.layout.view_key_subkey_item, null);
|
||||
if (mDefaultTextColor == null) {
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
mDefaultTextColor = keyId.getTextColors();
|
||||
@@ -177,13 +204,21 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
// Disable selection of items, http://stackoverflow.com/a/4075045
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
if (mSaveKeyringParcel == null) {
|
||||
return false;
|
||||
} else {
|
||||
return super.areAllItemsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable selection of items, http://stackoverflow.com/a/4075045
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return false;
|
||||
if (mSaveKeyringParcel == null) {
|
||||
return false;
|
||||
} else {
|
||||
return super.isEnabled(position);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ import java.util.ArrayList;
|
||||
|
||||
public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemClickListener {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private final ArrayList<Boolean> mCheckStates;
|
||||
|
||||
private SaveKeyringParcel mSaveKeyringParcel;
|
||||
|
||||
public static final String[] USER_IDS_PROJECTION = new String[]{
|
||||
@@ -60,7 +58,6 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
private static final int INDEX_IS_PRIMARY = 4;
|
||||
private static final int INDEX_IS_REVOKED = 5;
|
||||
|
||||
|
||||
public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes,
|
||||
SaveKeyringParcel saveKeyringParcel) {
|
||||
super(context, c, flags);
|
||||
@@ -134,15 +131,18 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
|
||||
// for edit key
|
||||
if (mSaveKeyringParcel != null) {
|
||||
boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null);
|
||||
boolean changeThisPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.changePrimaryUserId.equals(userId));
|
||||
boolean revokeThisUserId = (mSaveKeyringParcel.revokeUserIds.contains(userId));
|
||||
boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null);
|
||||
boolean changeThisPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.mChangePrimaryUserId.equals(userId));
|
||||
boolean revokeThisUserId = (mSaveKeyringParcel.mRevokeUserIds.contains(userId));
|
||||
|
||||
// only if primary user id will be changed
|
||||
// (this is not triggered if the user id is currently the primary one)
|
||||
if (changeAnyPrimaryUserId) {
|
||||
// change all user ids, only this one should be primary
|
||||
// change _all_ primary user ids and set new one to true
|
||||
isPrimary = changeThisPrimaryUserId;
|
||||
}
|
||||
|
||||
if (revokeThisUserId) {
|
||||
if (!isRevoked) {
|
||||
isRevoked = true;
|
||||
@@ -233,7 +233,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = mInflater.inflate(R.layout.view_key_userids_item, null);
|
||||
View view = mInflater.inflate(R.layout.view_key_user_id_item, null);
|
||||
// only need to do this once ever, since mShowCheckBoxes is final
|
||||
view.findViewById(R.id.checkBox).setVisibility(mCheckStates != null ? View.VISIBLE : View.GONE);
|
||||
return view;
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.dialog;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class ChangeExpiryDialogFragment extends DialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_CREATION_DATE = "creation_date";
|
||||
private static final String ARG_EXPIRY_DATE = "expiry_date";
|
||||
|
||||
public static final int MESSAGE_NEW_EXPIRY_DATE = 1;
|
||||
public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date";
|
||||
|
||||
private Messenger mMessenger;
|
||||
private Calendar mCreationCal;
|
||||
private Calendar mExpiryCal;
|
||||
|
||||
private int mDatePickerResultCount = 0;
|
||||
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
|
||||
new DatePickerDialog.OnDateSetListener() {
|
||||
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||
// Note: Ignore results after the first one - android sends multiples.
|
||||
if (mDatePickerResultCount++ == 0) {
|
||||
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
selectedCal.set(year, monthOfYear, dayOfMonth);
|
||||
if (mExpiryCal != null) {
|
||||
long numDays = (selectedCal.getTimeInMillis() / 86400000)
|
||||
- (mExpiryCal.getTimeInMillis() / 86400000);
|
||||
if (numDays > 0) {
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime());
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
} else {
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime());
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public class ExpiryDatePickerDialog extends DatePickerDialog {
|
||||
|
||||
public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack,
|
||||
int year, int monthOfYear, int dayOfMonth) {
|
||||
super(context, callBack, year, monthOfYear, dayOfMonth);
|
||||
}
|
||||
|
||||
// set permanent title
|
||||
public void setTitle(CharSequence title) {
|
||||
super.setTitle(getContext().getString(R.string.expiry_date_dialog_title));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*/
|
||||
public static ChangeExpiryDialogFragment newInstance(Messenger messenger,
|
||||
Date creationDate, Date expiryDate) {
|
||||
ChangeExpiryDialogFragment frag = new ChangeExpiryDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
args.putSerializable(ARG_CREATION_DATE, creationDate);
|
||||
args.putSerializable(ARG_EXPIRY_DATE, expiryDate);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
Date creationDate = (Date) getArguments().getSerializable(ARG_CREATION_DATE);
|
||||
Date expiryDate = (Date) getArguments().getSerializable(ARG_EXPIRY_DATE);
|
||||
|
||||
mCreationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
mCreationCal.setTime(creationDate);
|
||||
mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
mExpiryCal.setTime(expiryDate);
|
||||
|
||||
/*
|
||||
* Using custom DatePickerDialog which overrides the setTitle because
|
||||
* the DatePickerDialog title is buggy (unix warparound bug).
|
||||
* See: https://code.google.com/p/android/issues/detail?id=49066
|
||||
*/
|
||||
DatePickerDialog dialog = new ExpiryDatePickerDialog(getActivity(),
|
||||
mExpiryDateSetListener, mExpiryCal.get(Calendar.YEAR), mExpiryCal.get(Calendar.MONTH),
|
||||
mExpiryCal.get(Calendar.DAY_OF_MONTH));
|
||||
mDatePickerResultCount = 0;
|
||||
dialog.setCancelable(true);
|
||||
dialog.setButton(Dialog.BUTTON_NEGATIVE,
|
||||
getActivity().getString(R.string.btn_no_date),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Note: Ignore results after the first one - android sends multiples.
|
||||
if (mDatePickerResultCount++ == 0) {
|
||||
// none expiry dates corresponds to a null message
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null);
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// setCalendarViewShown() is supported from API 11 onwards.
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Hide calendarView in tablets because of the unix warparound bug.
|
||||
dialog.getDatePicker().setCalendarViewShown(false);
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
// will crash with IllegalArgumentException if we set a min date
|
||||
// that is not before expiry
|
||||
if (mCreationCal != null && mCreationCal.before(mExpiryCal)) {
|
||||
dialog.getDatePicker().setMinDate(mCreationCal.getTime().getTime()
|
||||
+ DateUtils.DAY_IN_MILLIS);
|
||||
} else {
|
||||
// When created date isn't available
|
||||
dialog.getDatePicker().setMinDate(mExpiryCal.getTime().getTime()
|
||||
+ DateUtils.DAY_IN_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class EditSubkeyDialogFragment extends DialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
|
||||
public static final int MESSAGE_CHANGE_EXPIRY = 1;
|
||||
public static final int MESSAGE_REVOKE = 2;
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*/
|
||||
public static EditSubkeyDialogFragment newInstance(Messenger messenger) {
|
||||
EditSubkeyDialogFragment frag = new EditSubkeyDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity());
|
||||
CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_subkey);
|
||||
|
||||
builder.setTitle(R.string.edit_key_edit_subkey_title);
|
||||
builder.setItems(array, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case 0:
|
||||
sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null);
|
||||
break;
|
||||
case 1:
|
||||
sendMessageToHandler(MESSAGE_REVOKE, null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
// above can't be statically verified to have been set in all cases because
|
||||
// the catch clause doesn't return.
|
||||
try {
|
||||
userId = secretRing.getPrimaryUserId();
|
||||
userId = secretRing.getPrimaryUserIdWithFallback();
|
||||
} catch (PgpGeneralException e) {
|
||||
userId = null;
|
||||
}
|
||||
@@ -189,7 +189,8 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
// Early breakout if we are dealing with a symmetric key
|
||||
if (secretRing == null) {
|
||||
PassphraseCacheService.addCachedPassphrase(activity, Constants.key.symmetric, passphrase);
|
||||
PassphraseCacheService.addCachedPassphrase(activity, Constants.key.symmetric,
|
||||
passphrase, getString(R.string.passp_cache_notif_pwd));
|
||||
// also return passphrase back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_DATA_PASSPHRASE, passphrase);
|
||||
@@ -228,10 +229,18 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
// cache the new passphrase
|
||||
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
|
||||
PassphraseCacheService.addCachedPassphrase(activity, masterKeyId, passphrase);
|
||||
|
||||
try {
|
||||
PassphraseCacheService.addCachedPassphrase(activity, masterKeyId, passphrase,
|
||||
secretRing.getPrimaryUserIdWithFallback());
|
||||
} catch(PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "adding of a passhrase failed", e);
|
||||
}
|
||||
|
||||
if (unlockedSecretKey.getKeyId() != masterKeyId) {
|
||||
PassphraseCacheService.addCachedPassphrase(
|
||||
activity, unlockedSecretKey.getKeyId(), passphrase);
|
||||
activity, unlockedSecretKey.getKeyId(), passphrase,
|
||||
unlockedSecretKey.getPrimaryUserIdWithFallback());
|
||||
}
|
||||
|
||||
// also return passphrase back to activity
|
||||
|
||||
@@ -39,15 +39,21 @@ public class ProgressScaler implements Progressable {
|
||||
* Set progress of ProgressDialog by sending message to handler on UI thread
|
||||
*/
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
mWrapped.setProgress(resourceId, progress, mMax);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(resourceId, progress, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
mWrapped.setProgress(progress, max);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(progress, max);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user