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:
Vincent Breitmoser
2014-07-26 23:06:32 +02:00
174 changed files with 5350 additions and 1267 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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