Merge pull request #491 from openpgp-keychain/edit-key

Edit key changes
This commit is contained in:
Dominik Schürmann 2014-03-30 18:04:43 +02:00
commit 6b5d60e1f4
16 changed files with 1303 additions and 528 deletions

View File

@ -59,13 +59,30 @@ public class PgpConversionHelper {
* @return
*/
public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
PGPSecretKeyRing keyRing = (PGPSecretKeyRing) BytesToPGPKeyRing(keysBytes);
PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
Object obj = null;
ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
@SuppressWarnings("unchecked")
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
while (itr.hasNext()) {
keys.add(itr.next());
try {
while ((obj = factory.nextObject()) != null) {
PGPSecretKey secKey = null;
if(obj instanceof PGPSecretKey) {
if ((secKey = (PGPSecretKey)obj ) == null) {
Log.e(Constants.TAG, "No keys given!");
}
keys.add(secKey);
} else if(obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
PGPSecretKeyRing keyRing = null;
if ((keyRing = (PGPSecretKeyRing)obj) == null) {
Log.e(Constants.TAG, "No keys given!");
}
@SuppressWarnings("unchecked")
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
while (itr.hasNext()) {
keys.add(itr.next());
}
}
}
} catch (IOException e) {
}
return keys;

View File

@ -210,9 +210,8 @@ public class PgpKeyHelper {
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(creationDate);
calendar.add(Calendar.DATE, key.getValidDays());
Date expiryDate = calendar.getTime();
return expiryDate;
return calendar.getTime();
}
public static Date getExpiryDate(PGPSecretKey key) {
@ -292,6 +291,28 @@ public class PgpKeyHelper {
return userId;
}
public static int getKeyUsage(PGPSecretKey key)
{
return getKeyUsage(key.getPublicKey());
}
@SuppressWarnings("unchecked")
private static int getKeyUsage(PGPPublicKey key) {
int usage = 0;
if (key.getVersion() >= 4) {
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) continue;
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
if (hashed != null) usage |= hashed.getKeyFlags();
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
if (unhashed != null) usage |= unhashed.getKeyFlags();
}
}
return usage;
}
@SuppressWarnings("unchecked")
public static boolean isEncryptionKey(PGPPublicKey key) {
if (!key.isEncryptionKey()) {
@ -398,6 +419,36 @@ public class PgpKeyHelper {
return false;
}
public static boolean isAuthenticationKey(PGPSecretKey key) {
return isAuthenticationKey(key.getPublicKey());
}
@SuppressWarnings("unchecked")
public static boolean isAuthenticationKey(PGPPublicKey key) {
if (key.getVersion() <= 3) {
return true;
}
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
continue;
}
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
if (hashed != null && (hashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) {
return true;
}
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) {
return true;
}
}
return false;
}
public static boolean isCertificationKey(PGPSecretKey key) {
return isCertificationKey(key.getPublicKey());
}
@ -411,7 +462,7 @@ public class PgpKeyHelper {
}
public static String getAlgorithmInfo(int algorithm, int keySize) {
String algorithmStr = null;
String algorithmStr;
switch (algorithm) {
case PGPPublicKey.RSA_ENCRYPT:

View File

@ -17,12 +17,24 @@
package org.sufficientlysecure.keychain.pgp;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.TimeZone;
import android.content.Context;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPUtil;
@ -36,6 +48,12 @@ import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.util.Primes;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import android.content.Context;
import android.util.Pair;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Primes;
@ -52,8 +70,8 @@ import java.util.List;
import java.util.TimeZone;
public class PgpKeyOperation {
private Context mContext;
private ProgressDialogUpdater mProgress;
private final Context mContext;
private final ProgressDialogUpdater mProgress;
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
@ -71,34 +89,18 @@ public class PgpKeyOperation {
this.mProgress = progress;
}
public void updateProgress(int message, int current, int total) {
void updateProgress(int message, int current, int total) {
if (mProgress != null) {
mProgress.setProgress(message, current, total);
}
}
public void updateProgress(int current, int total) {
void updateProgress(int current, int total) {
if (mProgress != null) {
mProgress.setProgress(current, total);
}
}
/**
* Creates new secret key.
*
* @param algorithmChoice
* @param keySize
* @param passphrase
* @param isMasterKey
* @return
* @throws NoSuchAlgorithmException
* @throws PGPException
* @throws NoSuchProviderException
* @throws PgpGeneralException
* @throws InvalidAlgorithmParameterException
*/
// TODO: key flags?
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey)
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
@ -112,8 +114,8 @@ public class PgpKeyOperation {
passphrase = "";
}
int algorithm = 0;
KeyPairGenerator keyGen = null;
int algorithm;
KeyPairGenerator keyGen;
switch (algorithmChoice) {
case Id.choice.algorithm.dsa: {
@ -165,15 +167,12 @@ public class PgpKeyOperation {
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor);
return secKey;
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor);
}
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
String newPassPhrase) throws IOException, PGPException,
NoSuchProviderException {
String newPassPhrase) throws IOException, PGPException {
updateProgress(R.string.progress_building_key, 0, 100);
if (oldPassPhrase == null) {
@ -199,160 +198,82 @@ public class PgpKeyOperation {
}
public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
ArrayList<Integer> keysUsages, ArrayList<GregorianCalendar> keysExpiryDates,
PGPPublicKey oldPublicKey, String oldPassPhrase,
String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
PGPException, NoSuchAlgorithmException, SignatureException, IOException {
private void buildNewSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys, ArrayList<GregorianCalendar> keysExpiryDates, ArrayList<Integer> keysUsages, String newPassPhrase, String oldPassPhrase) throws PgpGeneralException,
PGPException, SignatureException, IOException {
Log.d(Constants.TAG, "userIds: " + userIds.toString());
int usageId = keysUsages.get(0);
boolean canSign;
String mainUserId = userIds.get(0);
updateProgress(R.string.progress_building_key, 0, 100);
PGPSecretKey masterKey = keys.get(0);
if (oldPassPhrase == null) {
oldPassPhrase = "";
}
if (newPassPhrase == null) {
newPassPhrase = "";
// this removes all userIds and certifications previously attached to the masterPublicKey
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID());
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
updateProgress(R.string.progress_certifying_master_key, 20, 100);
for (String userId : userIds) {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
}
updateProgress(R.string.progress_preparing_master_key, 10, 100);
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
// prepare keyring generator with given master public and secret key
PGPKeyRingGenerator keyGen;
PGPPublicKey masterPublicKey; {
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
String mainUserId = userIds.get(0);
hashedPacketsGen.setKeyFlags(true, usageId);
// prepare the master key pair
PGPKeyPair masterKeyPair; {
PGPSecretKey masterKey = keys.get(0);
// this removes all userIds and certifications previously attached to the masterPublicKey
PGPPublicKey tmpKey = masterKey.getPublicKey();
masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
// already done by code above:
// PGPPublicKey masterPublicKey = masterKey.getPublicKey();
// // Somehow, the PGPPublicKey already has an empty certification attached to it when the
// // keyRing is generated the first time, we remove that when it exists, before adding the
// new
// // ones
// PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey,
// "");
// if (masterPublicKeyRmCert != null) {
// masterPublicKey = masterPublicKeyRmCert;
// }
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
updateProgress(R.string.progress_certifying_master_key, 20, 100);
// re-add old certificates, or create new ones for new uids
for (String userId : userIds) {
// re-add certs for this uid, take a note if self-signed cert is in there
boolean foundSelfSign = false;
Iterator<PGPSignature> it = tmpKey.getSignaturesForID(userId);
if(it != null) for(PGPSignature sig : new IterableIterator<PGPSignature>(it)) {
if(sig.getKeyID() == masterPublicKey.getKeyID()) {
// already have a self sign? skip this other one, then.
// note: PGPKeyRingGenerator adds one cert for the main user id, which
// will lead to duplicates. unfortunately, if we add any other here
// first, that will change the main user id order...
if(foundSelfSign)
continue;
foundSelfSign = true;
}
Log.d(Constants.TAG, "adding old sig for " + userId + " from "
+ PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()));
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, sig);
}
// there was an old self-signed certificate for this uid
if(foundSelfSign)
continue;
Log.d(Constants.TAG, "generating self-signed cert for " + userId);
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
}
masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
}
PGPSignatureSubpacketGenerator hashedPacketsGen;
PGPSignatureSubpacketGenerator unhashedPacketsGen; {
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
int usageId = keysUsages.get(0);
boolean canEncrypt =
(usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA;
if (canEncrypt) {
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
}
hashedPacketsGen.setKeyFlags(true, keyFlags);
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
if (keysExpiryDates.get(0) != null) {
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
creationDate.setTime(masterPublicKey.getCreationTime());
GregorianCalendar expiryDate = keysExpiryDates.get(0);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays =
(expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralException(
mContext.getString(R.string.error_expiry_must_come_after_creation));
}
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
//do this explicitly, although since we're rebuilding,
hashedPacketsGen.setKeyExpirationTime(false, 0);
//this happens anyway
}
}
updateProgress(R.string.progress_building_master_key, 30, 100);
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
HashAlgorithmTags.SHA1);
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
// Build key encrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
newPassPhrase.toCharArray());
keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
if (keysExpiryDates.get(0) != null) {
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
creationDate.setTime(masterPublicKey.getCreationTime());
GregorianCalendar expiryDate = keysExpiryDates.get(0);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0)
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
//this happens anyway
}
updateProgress(R.string.progress_building_master_key, 30, 100);
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
HashAlgorithmTags.SHA1);
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
// Build key encrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
newPassPhrase.toCharArray());
PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
updateProgress(R.string.progress_adding_sub_keys, 40, 100);
for (int i = 1; i < keys.size(); ++i) {
@ -361,27 +282,21 @@ public class PgpKeyOperation {
PGPSecretKey subKey = keys.get(i);
PGPPublicKey subPublicKey = subKey.getPublicKey();
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
oldPassPhrase.toCharArray());
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor);
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
// TODO: now used without algorithm and creation time?! (APG 1)
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
int keyFlags = 0;
int usageId = keysUsages.get(i);
boolean canSign =
(usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
boolean canEncrypt =
(usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
usageId = keysUsages.get(i);
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
if (canSign) {
Date todayDate = new Date(); //both sig times the same
keyFlags |= KeyFlags.SIGN_DATA;
// cross-certify signing keys
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
@ -396,10 +311,7 @@ public class PgpKeyOperation {
subPublicKey);
unhashedPacketsGen.setEmbeddedSignature(false, certification);
}
if (canEncrypt) {
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
}
hashedPacketsGen.setKeyFlags(false, keyFlags);
hashedPacketsGen.setKeyFlags(false, usageId);
if (keysExpiryDates.get(i) != null) {
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
@ -407,16 +319,12 @@ public class PgpKeyOperation {
GregorianCalendar expiryDate = keysExpiryDates.get(i);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays =
(expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralException
(mContext.getString(R.string.error_expiry_must_come_after_creation));
}
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0)
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
//do this explicitly, although since we're rebuilding,
hashedPacketsGen.setKeyExpirationTime(false, 0);
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
//this happens anyway
}
@ -426,53 +334,339 @@ public class PgpKeyOperation {
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
updateProgress(R.string.progress_re_adding_certs, 80, 100);
// re-add certificates from old public key
// TODO: this only takes care of user id certificates, what about others?
PGPPublicKey pubkey = publicKeyRing.getPublicKey();
for(String uid : new IterableIterator<String>(pubkey.getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(oldPublicKey.getSignaturesForID(uid), true)) {
// but skip self certificates
if(sig.getKeyID() == pubkey.getKeyID())
continue;
pubkey = PGPPublicKey.addCertification(pubkey, uid, sig);
}
}
publicKeyRing = PGPPublicKeyRing.insertPublicKey(publicKeyRing, pubkey);
updateProgress(R.string.progress_saving_key_ring, 90, 100);
/* additional handy debug info
Log.d(Constants.TAG, " ------- in private key -------");
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
Log.d(Constants.TAG, " ------- in public key -------");
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
*/
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
ProviderHelper.saveKeyRing(mContext, publicKeyRing);
updateProgress(R.string.progress_done, 100, 100);
}
/**
* Certify the given pubkeyid with the given masterkeyid.
*
* @param masterKeyId Certifying key, must be available as secret key
* @param pubKeyId ID of public key to certify
* @param userIds User IDs to certify, must not be null or empty
* @param passphrase Passphrase of the secret key
* @return A keyring with added certifications
*/
public void buildSecretKey (SaveKeyringParcel saveParcel)throws PgpGeneralException,
PGPException, SignatureException, IOException {
updateProgress(R.string.progress_building_key, 0, 100);
PGPSecretKey masterKey = saveParcel.keys.get(0);
PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID());
PGPPublicKeyRing pKR = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, masterKey.getKeyID());
if (saveParcel.oldPassPhrase == null) {
saveParcel.oldPassPhrase = "";
}
if (saveParcel.newPassPhrase == null) {
saveParcel.newPassPhrase = "";
}
if (mKR == null) {
buildNewSecretKey(saveParcel.userIDs, saveParcel.keys, saveParcel.keysExpiryDates,
saveParcel.keysUsages, saveParcel.newPassPhrase, saveParcel.oldPassPhrase); //new Keyring
return;
}
/*
IDs - NB This might not need to happen later, if we change the way the primary ID is chosen
remove deleted ids
if the primary ID changed we need to:
remove all of the IDs from the keyring, saving their certifications
add them all in again, updating certs of IDs which have changed
else
remove changed IDs and add in with new certs
if the master key changed, we need to remove the primary ID certification, so we can add
the new one when it is generated, and they don't conflict
Keys
remove deleted keys
if a key is modified, re-sign it
do we need to remove and add in?
Todo
identify more things which need to be preserved - e.g. trust levels?
user attributes
*/
if (saveParcel.deletedKeys != null) {
for (PGPSecretKey dKey : saveParcel.deletedKeys) {
mKR = PGPSecretKeyRing.removeSecretKey(mKR, dKey);
}
}
masterKey = mKR.getSecretKey();
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
int usageId = saveParcel.keysUsages.get(0);
boolean canSign;
String mainUserId = saveParcel.userIDs.get(0);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(saveParcel.oldPassPhrase.toCharArray());
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
updateProgress(R.string.progress_certifying_master_key, 20, 100);
boolean anyIDChanged = false;
for (String delID : saveParcel.deletedIDs) {
anyIDChanged = true;
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, delID);
}
int userIDIndex = 0;
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setKeyFlags(true, usageId);
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
if (saveParcel.keysExpiryDates.get(0) != null) {
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
creationDate.setTime(masterPublicKey.getCreationTime());
GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(0);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0)
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
//this happens anyway
}
if (saveParcel.primaryIDChanged || !saveParcel.originalIDs.get(0).equals(saveParcel.userIDs.get(0))) {
anyIDChanged = true;
ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
for (String userId : saveParcel.userIDs) {
String origID = saveParcel.originalIDs.get(userIDIndex);
if (origID.equals(userId) && !userId.equals(saveParcel.originalPrimaryID) && userIDIndex != 0) {
Iterator<PGPSignature> origSigs = masterPublicKey.getSignaturesForID(origID); //TODO: make sure this iterator only has signatures we are interested in
while (origSigs.hasNext()) {
PGPSignature origSig = origSigs.next();
sigList.add(new Pair<String, PGPSignature>(origID, origSig));
}
} else {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
if (userIDIndex == 0) {
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
}
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
sigList.add(new Pair<String, PGPSignature>(userId, certification));
}
if (!origID.equals("")) {
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
}
userIDIndex++;
}
for (Pair<String, PGPSignature> toAdd : sigList) {
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
}
} else {
for (String userId : saveParcel.userIDs) {
String origID = saveParcel.originalIDs.get(userIDIndex);
if (!origID.equals(userId)) {
anyIDChanged = true;
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
if (userIDIndex == 0) {
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
}
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
if (!origID.equals("")) {
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
}
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
}
userIDIndex++;
}
}
ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
if (saveParcel.moddedKeys[0]) {
userIDIndex = 0;
for (String userId : saveParcel.userIDs) {
String origID = saveParcel.originalIDs.get(userIDIndex);
if (!(origID.equals(saveParcel.originalPrimaryID) && !saveParcel.primaryIDChanged)) {
Iterator<PGPSignature> sigs = masterPublicKey.getSignaturesForID(userId); //TODO: make sure this iterator only has signatures we are interested in
while (sigs.hasNext()) {
PGPSignature sig = sigs.next();
sigList.add(new Pair<String, PGPSignature>(userId, sig));
}
}
if (!userId.equals("")) {
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, userId);
}
userIDIndex++;
}
anyIDChanged = true;
}
//update the keyring with the new ID information
if (anyIDChanged) {
pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
}
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
updateProgress(R.string.progress_building_master_key, 30, 100);
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
HashAlgorithmTags.SHA1);
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
// Build key encryptor based on old passphrase, as some keys may be unchanged
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.oldPassPhrase.toCharArray());
//this generates one more signature than necessary...
PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
for (int i = 1; i < saveParcel.keys.size(); ++i) {
updateProgress(40 + 50 * i / saveParcel.keys.size(), 100);
if (saveParcel.moddedKeys[i]) {
PGPSecretKey subKey = saveParcel.keys.get(i);
PGPPublicKey subPublicKey = subKey.getPublicKey();
PBESecretKeyDecryptor keyDecryptor2;
if (saveParcel.newKeys[i]) {
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
"".toCharArray());
} else {
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.oldPassPhrase.toCharArray());
}
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
usageId = saveParcel.keysUsages.get(i);
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
if (canSign) {
Date todayDate = new Date(); //both sig times the same
// cross-certify signing keys
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, todayDate); //set inner creation time
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
subPublicKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = sGen.generateCertification(masterPublicKey,
subPublicKey);
unhashedPacketsGen.setEmbeddedSignature(false, certification);
}
hashedPacketsGen.setKeyFlags(false, usageId);
if (saveParcel.keysExpiryDates.get(i) != null) {
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
creationDate.setTime(subPublicKey.getCreationTime());
GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(i);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0)
throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
//this happens anyway
}
keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
//certifications will be discarded if the key is changed, because I think, for a start,
//they will be invalid. Binding certs are regenerated anyway, and other certs which
//need to be kept are on IDs and attributes
//TODO: don't let revoked keys be edited, other than removed - changing one would result in the
//revocation being wrong?
}
}
PGPSecretKeyRing updatedSecretKeyRing = keyGen.generateSecretKeyRing();
//finally, update the keyrings
Iterator<PGPSecretKey> itr = updatedSecretKeyRing.getSecretKeys();
while (itr.hasNext()) {
PGPSecretKey theNextKey = itr.next();
if ((theNextKey.isMasterKey() && saveParcel.moddedKeys[0]) || !theNextKey.isMasterKey()) {
mKR = PGPSecretKeyRing.insertSecretKey(mKR, theNextKey);
pKR = PGPPublicKeyRing.insertPublicKey(pKR, theNextKey.getPublicKey());
}
}
//replace lost IDs
if (saveParcel.moddedKeys[0]) {
masterPublicKey = mKR.getPublicKey();
for (Pair<String, PGPSignature> toAdd : sigList) {
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
}
pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
}
// Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.newPassPhrase.toCharArray());
//update the passphrase
mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
updateProgress(R.string.progress_saving_key_ring, 90, 100);
/* additional handy debug info
Log.d(Constants.TAG, " ------- in private key -------");
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
Log.d(Constants.TAG, " ------- in public key -------");
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
*/
ProviderHelper.saveKeyRing(mContext, mKR);
ProviderHelper.saveKeyRing(mContext, pKR);
updateProgress(R.string.progress_done, 100, 100);
}
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {

View File

@ -24,7 +24,12 @@ import android.net.Uri;
import android.os.RemoteException;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
@ -491,21 +496,22 @@ public class ProviderHelper {
/**
* Get empty status of master key of keyring by its row id
*/
public static boolean getSecretMasterKeyCanSign(Context context, long keyRingRowId) {
public static boolean getSecretMasterKeyCanCertify(Context context, long keyRingRowId) {
Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
return getMasterKeyCanSign(context, queryUri);
return getMasterKeyCanCertify(context, queryUri);
}
/**
* Private helper method to get master key private empty status of keyring by its row id
*/
public static boolean getMasterKeyCanSign(Context context, Uri queryUri) {
public static boolean getMasterKeyCanCertify(Context context, Uri queryUri) {
String[] projection = new String[]{
KeyRings.MASTER_KEY_ID,
"(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ " AND sign_keys." + Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY
+ " AND sign_keys." + Keys.CAN_CERTIFY + " = '1' AND " + Keys.IS_MASTER_KEY
+ " = 1) AS sign",};
ContentResolver cr = context.getContentResolver();
@ -904,4 +910,4 @@ public class ProviderHelper {
return signature;
}
}
}

View File

@ -105,15 +105,10 @@ public class KeychainIntentService extends IntentService
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
// save keyring
public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase";
public static final String SAVE_KEYRING_CURRENT_PASSPHRASE = "current_passphrase";
public static final String SAVE_KEYRING_USER_IDS = "user_ids";
public static final String SAVE_KEYRING_KEYS = "keys";
public static final String SAVE_KEYRING_KEYS_USAGES = "keys_usages";
public static final String SAVE_KEYRING_KEYS_EXPIRY_DATES = "keys_expiry_dates";
public static final String SAVE_KEYRING_MASTER_KEY_ID = "master_key_id";
public static final String SAVE_KEYRING_PARCEL = "save_parcel";
public static final String SAVE_KEYRING_CAN_SIGN = "can_sign";
// generate key
public static final String GENERATE_KEY_ALGORITHM = "algorithm";
public static final String GENERATE_KEY_KEY_SIZE = "key_size";
@ -516,8 +511,9 @@ public class KeychainIntentService extends IntentService
} else if (ACTION_SAVE_KEYRING.equals(action)) {
try {
/* Input */
String oldPassPhrase = data.getString(SAVE_KEYRING_CURRENT_PASSPHRASE);
String newPassPhrase = data.getString(SAVE_KEYRING_NEW_PASSPHRASE);
SaveKeyringParcel saveParams = data.getParcelable(SAVE_KEYRING_PARCEL);
String oldPassPhrase = saveParams.oldPassPhrase;
String newPassPhrase = saveParams.newPassPhrase;
boolean canSign = true;
if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
@ -527,14 +523,8 @@ public class KeychainIntentService extends IntentService
if (newPassPhrase == null) {
newPassPhrase = oldPassPhrase;
}
ArrayList<String> userIds = data.getStringArrayList(SAVE_KEYRING_USER_IDS);
ArrayList<PGPSecretKey> keys = PgpConversionHelper.BytesToPGPSecretKeyList(data
.getByteArray(SAVE_KEYRING_KEYS));
ArrayList<Integer> keysUsages = data.getIntegerArrayList(SAVE_KEYRING_KEYS_USAGES);
ArrayList<GregorianCalendar> keysExpiryDates =
(ArrayList<GregorianCalendar>) data.getSerializable(SAVE_KEYRING_KEYS_EXPIRY_DATES);
long masterKeyId = data.getLong(SAVE_KEYRING_MASTER_KEY_ID);
long masterKeyId = saveParams.keys.get(0).getKeyID();
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
/* Operation */
@ -543,17 +533,7 @@ public class KeychainIntentService extends IntentService
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
oldPassPhrase, newPassPhrase);
} else {
//TODO: Workaround due to ProviderHelper.getPGPPublicKeyByKeyId can not resolve public key of master-key id with uri/cursor
PGPPublicKey pubkey = null;
for(PGPSecretKey key : keys) {
PGPPublicKey tempKey = key.getPublicKey();
if (tempKey.getKeyID() == masterKeyId) {
pubkey = tempKey;
}
}
//PGPPublicKey pubkey = ProviderHelper.getPGPPublicKeyByKeyId(this, masterKeyId);
keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates,
pubkey, oldPassPhrase, newPassPhrase);
keyOperations.buildSecretKey(saveParams);
}
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2014 Ash Hughes <ashes-iontach@hotmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
import android.os.Parcelable;
import org.spongycastle.openpgp.PGPSecretKey;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import java.util.ArrayList;
import java.util.GregorianCalendar;
public class SaveKeyringParcel implements Parcelable {
public ArrayList<String> userIDs;
public ArrayList<String> originalIDs;
public ArrayList<String> deletedIDs;
public boolean primaryIDChanged;
public boolean[] moddedKeys;
public ArrayList<PGPSecretKey> deletedKeys;
public ArrayList<GregorianCalendar> keysExpiryDates;
public ArrayList<Integer> keysUsages;
public String newPassPhrase;
public String oldPassPhrase;
public boolean[] newKeys;
public ArrayList<PGPSecretKey> keys;
public String originalPrimaryID;
public SaveKeyringParcel() {}
private SaveKeyringParcel(Parcel source)
{
userIDs = (ArrayList<String>)source.readSerializable();
originalIDs = (ArrayList<String>)source.readSerializable();
deletedIDs = (ArrayList<String>)source.readSerializable();
primaryIDChanged = source.readByte() != 0;
moddedKeys = source.createBooleanArray();
byte[] tmp = source.createByteArray();
if (tmp == null)
deletedKeys = null;
else
deletedKeys = PgpConversionHelper.BytesToPGPSecretKeyList(tmp);
keysExpiryDates = (ArrayList<GregorianCalendar>)source.readSerializable();
keysUsages = source.readArrayList(Integer.class.getClassLoader());
newPassPhrase = source.readString();
oldPassPhrase = source.readString();
newKeys = source.createBooleanArray();
keys = PgpConversionHelper.BytesToPGPSecretKeyList(source.createByteArray());
originalPrimaryID = source.readString();
}
@Override
public void writeToParcel(Parcel destination, int flags)
{
destination.writeSerializable(userIDs); //might not be the best method to store.
destination.writeSerializable(originalIDs);
destination.writeSerializable(deletedIDs);
destination.writeByte((byte) (primaryIDChanged ? 1 : 0));
destination.writeBooleanArray(moddedKeys);
byte[] tmp = null;
if (deletedKeys.size() != 0)
tmp = PgpConversionHelper.PGPSecretKeyArrayListToBytes(deletedKeys);
destination.writeByteArray(tmp);
destination.writeSerializable(keysExpiryDates);
destination.writeList(keysUsages);
destination.writeString(newPassPhrase);
destination.writeString(oldPassPhrase);
destination.writeBooleanArray(newKeys);
destination.writeByteArray(PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys));
destination.writeString(originalPrimaryID);
}
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
public SaveKeyringParcel createFromParcel(final Parcel source) {
return new SaveKeyringParcel(source);
}
public SaveKeyringParcel[] newArray(final int size) {
return new SaveKeyringParcel[size];
}
};
@Override
public int describeContents()
{
return 0;
}
}

View File

@ -17,6 +17,12 @@
package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Vector;
import org.spongycastle.bcpg.sig.KeyFlags;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
@ -41,7 +47,6 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
@ -51,19 +56,27 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor;
import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
import org.sufficientlysecure.keychain.ui.widget.SectionView;
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.Vector;
public class EditKeyActivity extends ActionBarActivity {
import android.app.AlertDialog;
import android.support.v4.app.ActivityCompat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
public class EditKeyActivity extends ActionBarActivity implements EditorListener {
// Actions for internal use only:
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
@ -90,6 +103,9 @@ public class EditKeyActivity extends ActionBarActivity {
private String mNewPassPhrase = null;
private String mSavedNewPassPhrase = null;
private boolean mIsPassPhraseSet;
private boolean mNeedsSaving;
private boolean mIsBrandNewKeyring = false;
private MenuItem mSaveButton;
private BootstrapButton mChangePassphrase;
@ -102,12 +118,42 @@ public class EditKeyActivity extends ActionBarActivity {
ExportHelper mExportHelper;
public boolean needsSaving()
{
mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving();
mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving();
mNeedsSaving |= hasPassphraseChanged();
mNeedsSaving |= mIsBrandNewKeyring;
return mNeedsSaving;
}
public void somethingChanged()
{
ActivityCompat.invalidateOptionsMenu(this);
//Toast.makeText(this, "Needs saving: " + Boolean.toString(mNeedsSaving) + "(" + Boolean.toString(mUserIdsView.needsSaving()) + ", " + Boolean.toString(mKeysView.needsSaving()) + ")", Toast.LENGTH_LONG).show();
}
public void onDeleted(Editor e, boolean wasNewItem)
{
somethingChanged();
}
public void onEdited()
{
somethingChanged();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mExportHelper = new ExportHelper(this);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setIcon(android.R.color.transparent);
getSupportActionBar().setHomeButtonEnabled(true);
mUserIds = new Vector<String>();
mKeys = new Vector<PGPSecretKey>();
mKeysUsages = new Vector<Integer>();
@ -128,24 +174,10 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent
*/
private void handleActionCreateKey(Intent intent) {
// Inflate a "Save"/"Cancel" custom action bar
ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() {
@Override
public void onClick(View v) {
saveClicked();
}
}, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override
public void onClick(View v) {
cancelClicked();
}
}
);
Bundle extras = intent.getExtras();
mCurrentPassphrase = "";
mIsBrandNewKeyring = true;
if (extras != null) {
// if userId is given, prefill the fields
@ -203,22 +235,24 @@ public class EditKeyActivity extends ActionBarActivity {
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get new key from data bundle returned from service
Bundle data = message.getData();
PGPSecretKey masterKey = (PGPSecretKey) PgpConversionHelper
PGPSecretKey masterKey = PgpConversionHelper
.BytesToPGPSecretKey(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
PGPSecretKey subKey = (PGPSecretKey) PgpConversionHelper
PGPSecretKey subKey = PgpConversionHelper
.BytesToPGPSecretKey(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY2));
//We must set the key flags here as they are not set when we make the
//key pair. Because we are not generating hashed packets there...
// add master key
mKeys.add(masterKey);
mKeysUsages.add(Id.choice.usage.sign_only); //TODO: get from key flags
mKeysUsages.add(KeyFlags.CERTIFY_OTHER);
// add sub key
mKeys.add(subKey);
mKeysUsages.add(Id.choice.usage.encrypt_only); //TODO: get from key flags
mKeysUsages.add(KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE);
buildLayout();
buildLayout(true);
}
}
};
@ -234,7 +268,7 @@ public class EditKeyActivity extends ActionBarActivity {
}
}
} else {
buildLayout();
buildLayout(false);
}
}
@ -244,40 +278,29 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent
*/
private void handleActionEditKey(Intent intent) {
// Inflate a "Save"/"Cancel" custom action bar
ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() {
@Override
public void onClick(View v) {
saveClicked();
}
});
mDataUri = intent.getData();
if (mDataUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
finish();
return;
} else {
Log.d(Constants.TAG, "uri: " + mDataUri);
// get master key id using row id
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
mMasterCanSign = ProviderHelper.getMasterKeyCanSign(this, mDataUri);
finallyEdit(masterKeyId, mMasterCanSign);
mMasterCanSign = ProviderHelper.getMasterKeyCanCertify(this, mDataUri);
finallyEdit(masterKeyId);
}
}
private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) {
private void showPassphraseDialog(final long masterKeyId) {
// Message is received after passphrase is cached
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
String passphrase = PassphraseCacheService.getCachedPassphrase(
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
EditKeyActivity.this, masterKeyId);
mCurrentPassphrase = passphrase;
finallySaveClicked();
}
}
@ -298,60 +321,66 @@ public class EditKeyActivity extends ActionBarActivity {
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// show menu only on edit
if (mDataUri != null) {
return super.onPrepareOptionsMenu(menu);
} else {
return false;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.key_edit, menu);
mSaveButton = menu.findItem(R.id.menu_key_edit_save);
mSaveButton.setEnabled(needsSaving());
//totally get rid of some actions for new keys
if (mDataUri == null) {
MenuItem mButton = menu.findItem(R.id.menu_key_edit_export_file);
mButton.setVisible(false);
mButton = menu.findItem(R.id.menu_key_edit_delete);
mButton.setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_key_edit_cancel:
cancelClicked();
return true;
case R.id.menu_key_edit_export_file:
case android.R.id.home:
cancelClicked(); //TODO: why isn't this triggered on my tablet - one of many ui problems I've had with this device. A code compatibility issue or a Samsung fail?
return true;
case R.id.menu_key_edit_cancel:
cancelClicked();
return true;
case R.id.menu_key_edit_export_file:
if (needsSaving()) {
Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show();
} else {
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
long[] ids = new long[]{masterKeyId};
mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC,
null);
return true;
case R.id.menu_key_edit_delete: {
//Convert the uri to one based on rowId
long rowId= ProviderHelper.getRowId(this,mDataUri);
Uri convertUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
// Message is received after key is deleted
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
setResult(RESULT_CANCELED);
finish();
}
}
};
mExportHelper.deleteKey(convertUri, returnHandler);
return true;
}
return true;
case R.id.menu_key_edit_delete:
long rowId= ProviderHelper.getRowId(this,mDataUri);
Uri convertUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
// Message is received after key is deleted
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
setResult(RESULT_CANCELED);
finish();
}
}};
mExportHelper.deleteKey(convertUri, returnHandler);
return true;
case R.id.menu_key_edit_save:
saveClicked();
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("unchecked")
private void finallyEdit(final long masterKeyId, final boolean masterCanSign) {
private void finallyEdit(final long masterKeyId) {
if (masterKeyId != 0) {
PGPSecretKey masterKey = null;
mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId);
@ -366,16 +395,24 @@ public class EditKeyActivity extends ActionBarActivity {
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
}
if (masterKey != null) {
boolean isSet = false;
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
Log.d(Constants.TAG, "Added userId " + userId);
if (!isSet) {
isSet = true;
String[] parts = PgpKeyHelper.splitUserId(userId);
if (parts[0] != null)
setTitle(parts[0]);
}
mUserIds.add(userId);
}
}
}
mCurrentPassphrase = "";
buildLayout(false);
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
buildLayout();
if (!mIsPassPhraseSet) {
// check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true);
@ -399,6 +436,7 @@ public class EditKeyActivity extends ActionBarActivity {
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
updatePassPhraseButtonText();
somethingChanged();
}
}
};
@ -407,7 +445,7 @@ public class EditKeyActivity extends ActionBarActivity {
Messenger messenger = new Messenger(returnHandler);
// set title based on isPassphraseSet()
int title = -1;
int title;
if (isPassphraseSet()) {
title = R.string.title_change_passphrase;
} else {
@ -423,8 +461,9 @@ public class EditKeyActivity extends ActionBarActivity {
/**
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
* id and key.
* @param newKeys
*/
private void buildLayout() {
private void buildLayout(boolean newKeys) {
setContentView(R.layout.edit_key_activity);
// find views
@ -442,11 +481,13 @@ public class EditKeyActivity extends ActionBarActivity {
mUserIdsView.setType(Id.type.user_id);
mUserIdsView.setCanEdit(mMasterCanSign);
mUserIdsView.setUserIds(mUserIds);
mUserIdsView.setEditorListener(this);
container.addView(mUserIdsView);
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mKeysView.setType(Id.type.key);
mKeysView.setCanEdit(mMasterCanSign);
mKeysView.setKeys(mKeys, mKeysUsages);
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
mKeysView.setEditorListener(this);
container.addView(mKeysView);
updatePassPhraseButtonText();
@ -457,7 +498,7 @@ public class EditKeyActivity extends ActionBarActivity {
}
});
// disable passphrase when no passphrase checkobox is checked!
// disable passphrase when no passphrase checkbox is checked!
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
@ -471,6 +512,7 @@ public class EditKeyActivity extends ActionBarActivity {
mNewPassPhrase = mSavedNewPassPhrase;
mChangePassphrase.setVisibility(View.VISIBLE);
}
somethingChanged();
}
});
}
@ -493,27 +535,54 @@ public class EditKeyActivity extends ActionBarActivity {
}
}
public boolean hasPassphraseChanged()
{
if (mNoPassphrase != null) {
if (mNoPassphrase.isChecked()) {
return mIsPassPhraseSet;
} else {
return (mNewPassPhrase != null && !mNewPassPhrase.equals(""));
}
}else {
return false;
}
}
private void saveClicked() {
long masterKeyId = getMasterKeyId();
if (!isPassphraseSet()) {
Log.e(Constants.TAG, "No passphrase has been set");
Toast.makeText(this, R.string.set_a_passphrase, Toast.LENGTH_LONG).show();
} else {
String passphrase = null;
if (mIsPassPhraseSet) {
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
} else {
passphrase = "";
}
if (passphrase == null) {
showPassphraseDialog(masterKeyId, mMasterCanSign);
} else {
mCurrentPassphrase = passphrase;
finallySaveClicked();
if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
try {
if (!isPassphraseSet()) {
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
}
String passphrase;
if (mIsPassPhraseSet)
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
else
passphrase = "";
if (passphrase == null) {
showPassphraseDialog(masterKeyId);
} else {
mCurrentPassphrase = passphrase;
finallySaveClicked();
}
} catch (PgpGeneralException e) {
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
Toast.LENGTH_SHORT).show();
}
}
}
private boolean[] toPrimitiveArray(final List<Boolean> booleanList) {
final boolean[] primitives = new boolean[booleanList.size()];
int index = 0;
for (Boolean object : booleanList) {
primitives[index++] = object;
}
return primitives;
}
private void finallySaveClicked() {
try {
// Send all information needed to service to edit key in other thread
@ -521,22 +590,26 @@ public class EditKeyActivity extends ActionBarActivity {
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
SaveKeyringParcel saveParams = new SaveKeyringParcel();
saveParams.userIDs = getUserIds(mUserIdsView);
saveParams.originalIDs = mUserIdsView.getOriginalIDs();
saveParams.deletedIDs = mUserIdsView.getDeletedIDs();
saveParams.primaryIDChanged = mUserIdsView.primaryChanged();
saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray());
saveParams.deletedKeys = mKeysView.getDeletedKeys();
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
saveParams.keysUsages = getKeysUsages(mKeysView);
saveParams.newPassPhrase = mNewPassPhrase;
saveParams.oldPassPhrase = mCurrentPassphrase;
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
saveParams.keys = getKeys(mKeysView);
saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID();
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE,
mCurrentPassphrase);
data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase);
data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS,
getUserIds(mUserIdsView));
ArrayList<PGPSecretKey> keys = getKeys(mKeysView);
data.putByteArray(KeychainIntentService.SAVE_KEYRING_KEYS,
PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys));
data.putIntegerArrayList(KeychainIntentService.SAVE_KEYRING_KEYS_USAGES,
getKeysUsages(mKeysView));
data.putSerializable(KeychainIntentService.SAVE_KEYRING_KEYS_EXPIRY_DATES,
getKeysExpiryDates(mKeysView));
data.putLong(KeychainIntentService.SAVE_KEYRING_MASTER_KEY_ID, getMasterKeyId());
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@ -579,8 +652,35 @@ public class EditKeyActivity extends ActionBarActivity {
}
private void cancelClicked() {
setResult(RESULT_CANCELED);
finish();
if (needsSaving()) { //ask if we want to save
AlertDialog.Builder alert = new AlertDialog.Builder(
EditKeyActivity.this);
alert.setIcon(android.R.drawable.ic_dialog_alert);
alert.setTitle(R.string.warning);
alert.setMessage(EditKeyActivity.this.getString(R.string.ask_save_changed_key));
alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
saveClicked();
}
});
alert.setNegativeButton(this.getString(android.R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
setResult(RESULT_CANCELED);
finish();
}
});
alert.setCancelable(false);
alert.create().show();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
/**
@ -597,19 +697,8 @@ public class EditKeyActivity extends ActionBarActivity {
boolean gotMainUserId = false;
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
String userId = null;
try {
userId = editor.getValue();
} catch (UserIdEditor.NoNameException e) {
throw new PgpGeneralException(this.getString(R.string.error_user_id_needs_a_name));
} catch (UserIdEditor.NoEmailException e) {
throw new PgpGeneralException(
this.getString(R.string.error_user_id_needs_an_email_address));
}
if (userId.equals("")) {
continue;
}
String userId;
userId = editor.getValue();
if (editor.isMainUserId()) {
userIds.add(0, userId);
@ -698,4 +787,4 @@ public class EditKeyActivity extends ActionBarActivity {
: getString(R.string.btn_set_passphrase));
}
}
}

View File

@ -91,10 +91,15 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
}
}
public void onDeleted(Editor editor) {
public void onDeleted(Editor editor, boolean wasNewItem) {
// nothing to do
}
@Override
public void onEdited() {
}
public void onClick(View v) {
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
mEditors, false);

View File

@ -18,8 +18,10 @@ package org.sufficientlysecure.keychain.ui.widget;
public interface Editor {
public interface EditorListener {
public void onDeleted(Editor editor);
public void onDeleted(Editor editor, boolean wasNewItem);
public void onEdited();
}
public void setEditorListener(EditorListener listener);
public boolean needsSaving();
}

View File

@ -16,6 +16,20 @@
package org.sufficientlysecure.keychain.ui.widget;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.Vector;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyFlags;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.util.Choice;
import android.annotation.TargetApi;
import android.app.DatePickerDialog;
import android.app.Dialog;
@ -26,7 +40,13 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.*;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
@ -47,11 +67,30 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
BootstrapButton mDeleteButton;
TextView mAlgorithm;
TextView mKeyId;
Spinner mUsage;
TextView mCreationDate;
BootstrapButton mExpiryDateButton;
GregorianCalendar mCreatedDate;
GregorianCalendar mExpiryDate;
GregorianCalendar mOriginalExpiryDate = null;
CheckBox mChkCertify;
CheckBox mChkSign;
CheckBox mChkEncrypt;
CheckBox mChkAuthenticate;
int mUsage;
int mOriginalUsage;
boolean mIsNewKey;
private CheckBox.OnCheckedChangeListener mCheckChanged = new CheckBox.OnCheckedChangeListener()
{
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
};
private int mDatePickerResultCount = 0;
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
@ -61,7 +100,18 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
if (mDatePickerResultCount++ == 0) {
GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
date.set(year, monthOfYear, dayOfMonth);
setExpiryDate(date);
if (mOriginalExpiryDate != null) {
long numDays = (date.getTimeInMillis() / 86400000) - (mOriginalExpiryDate.getTimeInMillis() / 86400000);
if (numDays == 0)
setExpiryDate(mOriginalExpiryDate);
else
setExpiryDate(date);
} else {
setExpiryDate(date);
}
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}
};
@ -83,21 +133,17 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
mKeyId = (TextView) findViewById(R.id.keyId);
mCreationDate = (TextView) findViewById(R.id.creation);
mExpiryDateButton = (BootstrapButton) findViewById(R.id.expiry);
mUsage = (Spinner) findViewById(R.id.usage);
Choice choices[] = {
new Choice(Id.choice.usage.sign_only, getResources().getString(
R.string.choice_sign_only)),
new Choice(Id.choice.usage.encrypt_only, getResources().getString(
R.string.choice_encrypt_only)),
new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
R.string.choice_sign_and_encrypt)), };
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mUsage.setAdapter(adapter);
mDeleteButton = (BootstrapButton) findViewById(R.id.delete);
mDeleteButton.setOnClickListener(this);
mChkCertify = (CheckBox) findViewById(R.id.chkCertify);
mChkCertify.setOnCheckedChangeListener(mCheckChanged);
mChkSign = (CheckBox) findViewById(R.id.chkSign);
mChkSign.setOnCheckedChangeListener(mCheckChanged);
mChkEncrypt = (CheckBox) findViewById(R.id.chkEncrypt);
mChkEncrypt.setOnCheckedChangeListener(mCheckChanged);
mChkAuthenticate = (CheckBox) findViewById(R.id.chkAuthenticate);
mChkAuthenticate.setOnCheckedChangeListener(mCheckChanged);
setExpiryDate(null);
@ -125,6 +171,9 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
// Note: Ignore results after the first one - android sends multiples.
if (mDatePickerResultCount++ == 0) {
setExpiryDate(null);
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}
});
@ -155,12 +204,14 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
public void setCanEdit(boolean bCanEdit) {
if (!bCanEdit) {
mDeleteButton.setVisibility(View.INVISIBLE);
mUsage.setEnabled(false);
mExpiryDateButton.setEnabled(false);
mChkSign.setEnabled(false); //certify is always disabled
mChkEncrypt.setEnabled(false);
mChkAuthenticate.setEnabled(false);
}
}
public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) {
public void setValue(PGPSecretKey key, boolean isMasterKey, int usage, boolean isNewKey) {
mKey = key;
mIsMasterKey = isMasterKey;
@ -175,47 +226,45 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
Vector<Choice> choices = new Vector<Choice>();
boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA);
if (!isElGamalKey) {
choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString(
R.string.choice_sign_only)));
if (isElGamalKey) {
mChkSign.setVisibility(View.INVISIBLE);
TableLayout table = (TableLayout)findViewById(R.id.table_keylayout);
TableRow row = (TableRow)findViewById(R.id.row_sign);
table.removeView(row);
}
if (!mIsMasterKey && !isDSAKey) {
choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString(
R.string.choice_encrypt_only)));
if (isDSAKey) {
mChkEncrypt.setVisibility(View.INVISIBLE);
TableLayout table = (TableLayout)findViewById(R.id.table_keylayout);
TableRow row = (TableRow)findViewById(R.id.row_encrypt);
table.removeView(row);
}
if (!isElGamalKey && !isDSAKey) {
choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
R.string.choice_sign_and_encrypt)));
}
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mUsage.setAdapter(adapter);
// Set value in choice dropdown to key
int selectId = 0;
if (PgpKeyHelper.isEncryptionKey(key)) {
if (PgpKeyHelper.isSigningKey(key)) {
selectId = Id.choice.usage.sign_and_encrypt;
} else {
selectId = Id.choice.usage.encrypt_only;
}
if (!mIsMasterKey) {
mChkCertify.setVisibility(View.INVISIBLE);
TableLayout table = (TableLayout)findViewById(R.id.table_keylayout);
TableRow row = (TableRow)findViewById(R.id.row_certify);
table.removeView(row);
} else {
// set usage if it is predefined
if (usage != -1) {
selectId = usage;
} else {
selectId = Id.choice.usage.sign_only;
}
TextView mLabelUsage2= (TextView) findViewById(R.id.label_usage2);
mLabelUsage2.setVisibility(View.INVISIBLE);
}
for (int i = 0; i < choices.size(); ++i) {
if (choices.get(i).getId() == selectId) {
mUsage.setSelection(i);
break;
}
int selectId = 0;
mIsNewKey = isNewKey;
if (isNewKey) {
mUsage = usage;
mChkCertify.setChecked((usage &= KeyFlags.CERTIFY_OTHER) == KeyFlags.CERTIFY_OTHER);
mChkSign.setChecked((usage &= KeyFlags.SIGN_DATA) == KeyFlags.SIGN_DATA);
mChkEncrypt.setChecked(((usage &= KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) ||
((usage &= KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE));
mChkAuthenticate.setChecked((usage &= KeyFlags.AUTHENTICATION) == KeyFlags.AUTHENTICATION);
} else {
mUsage = PgpKeyHelper.getKeyUsage(key);
mOriginalUsage = mUsage;
if (key.isMasterKey())
mChkCertify.setChecked(PgpKeyHelper.isCertificationKey(key));
mChkSign.setChecked(PgpKeyHelper.isSigningKey(key));
mChkEncrypt.setChecked(PgpKeyHelper.isEncryptionKey(key));
mChkAuthenticate.setChecked(PgpKeyHelper.isAuthenticationKey(key));
}
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
@ -228,6 +277,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
} else {
cal.setTime(PgpKeyHelper.getExpiryDate(key));
setExpiryDate(cal);
mOriginalExpiryDate = cal;
}
}
@ -241,7 +291,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
if (v == mDeleteButton) {
parent.removeView(this);
if (mEditorListener != null) {
mEditorListener.onDeleted(this);
mEditorListener.onDeleted(this, mIsNewKey);
}
}
}
@ -273,7 +323,41 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
}
public int getUsage() {
return ((Choice) mUsage.getSelectedItem()).getId();
mUsage = (mUsage & ~KeyFlags.CERTIFY_OTHER) | (mChkCertify.isChecked() ? KeyFlags.CERTIFY_OTHER : 0);
mUsage = (mUsage & ~KeyFlags.SIGN_DATA) | (mChkSign.isChecked() ? KeyFlags.SIGN_DATA : 0);
mUsage = (mUsage & ~KeyFlags.ENCRYPT_COMMS) | (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_COMMS : 0);
mUsage = (mUsage & ~KeyFlags.ENCRYPT_STORAGE) | (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_STORAGE : 0);
mUsage = (mUsage & ~KeyFlags.AUTHENTICATION) | (mChkAuthenticate.isChecked() ? KeyFlags.AUTHENTICATION : 0);
return mUsage;
}
public boolean needsSaving()
{
if (mIsNewKey)
return true;
boolean retval = (getUsage() != mOriginalUsage);
boolean dateChanged;
boolean mOEDNull = (mOriginalExpiryDate == null);
boolean mEDNull = (mExpiryDate == null);
if (mOEDNull != mEDNull) {
dateChanged = true;
} else {
if(mOEDNull) //both null, no change
dateChanged = false;
else
dateChanged = ((mExpiryDate.compareTo(mOriginalExpiryDate)) != 0);
}
retval |= dateChanged;
return retval;
}
public boolean getIsNewKey()
{
return mIsNewKey;
}
}

View File

@ -66,11 +66,16 @@ public class KeyServerEditor extends LinearLayout implements Editor, OnClickList
if (v == mDeleteButton) {
parent.removeView(this);
if (mEditorListener != null) {
mEditorListener.onDeleted(this);
mEditorListener.onDeleted(this, false);
}
}
}
@Override
public boolean needsSaving() {
return false;
}
public void setEditorListener(EditorListener listener) {
mEditorListener = listener;
}

View File

@ -16,6 +16,23 @@
package org.sufficientlysecure.keychain.ui.widget;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import org.spongycastle.openpgp.PGPKeyFlags;
import org.spongycastle.openpgp.PGPSecretKey;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.util.Choice;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
@ -44,23 +61,31 @@ import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.util.Choice;
import java.util.Vector;
public class SectionView extends LinearLayout implements OnClickListener, EditorListener {
public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Editor {
private LayoutInflater mInflater;
private BootstrapButton mPlusButton;
private ViewGroup mEditors;
private TextView mTitle;
private int mType = 0;
private EditorListener mEditorListener = null;
private Choice mNewKeyAlgorithmChoice;
private int mNewKeySize;
private boolean oldItemDeleted = false;
private ArrayList<String> mDeletedIDs = new ArrayList<String>();
private ArrayList<PGPSecretKey> mDeletedKeys = new ArrayList<PGPSecretKey>();
private boolean mCanEdit = true;
private ActionBarActivity mActivity;
private ProgressDialogFragment mGeneratingDialog;
public void setEditorListener(EditorListener listener) {
mEditorListener = listener;
}
public SectionView(Context context) {
super(context);
mActivity = (ActionBarActivity) context;
@ -124,8 +149,26 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
/**
* {@inheritDoc}
*/
public void onDeleted(Editor editor) {
public void onDeleted(Editor editor, boolean wasNewItem) {
oldItemDeleted |= !wasNewItem;
if (oldItemDeleted) {
if (mType == Id.type.user_id)
mDeletedIDs.add(((UserIdEditor)editor).getOriginalID());
else if (mType == Id.type.key)
mDeletedKeys.add(((KeyEditor)editor).getValue());
}
this.updateEditorsVisible();
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
@Override
public void onEdited() {
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
protected void updateEditorsVisible() {
@ -133,6 +176,94 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
}
public boolean needsSaving()
{
//check each view for needs saving, take account of deleted items
boolean ret = oldItemDeleted;
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
ret |= editor.needsSaving();
if (mType == Id.type.user_id)
ret |= ((UserIdEditor)editor).primarySwapped();
}
return ret;
}
public boolean primaryChanged()
{
boolean ret = false;
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
if (mType == Id.type.user_id)
ret |= ((UserIdEditor)editor).primarySwapped();
}
return ret;
}
public String getOriginalPrimaryID()
{ //NB: this will have to change when we change how Primary IDs are chosen, and so we need to be
// careful about where Master key capabilities are stored... multiple primaries and
// revoked ones make this harder than the simple case we are continuing to assume here
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
if (mType == Id.type.user_id) {
if(((UserIdEditor)editor).getIsOriginallyMainUserID()) {
return ((UserIdEditor)editor).getOriginalID();
}
}
}
return null;
}
public ArrayList<String> getOriginalIDs()
{
ArrayList<String> orig = new ArrayList<String>();
if (mType == Id.type.user_id) {
for (int i = 0; i < mEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) mEditors.getChildAt(i);
if (editor.isMainUserId())
orig.add(0, editor.getOriginalID());
else
orig.add(editor.getOriginalID());
}
return orig;
} else {
return null;
}
}
public ArrayList<String> getDeletedIDs()
{
return mDeletedIDs;
}
public ArrayList<PGPSecretKey> getDeletedKeys()
{
return mDeletedKeys;
}
public List<Boolean> getNeedsSavingArray()
{
ArrayList<Boolean> mList = new ArrayList<Boolean>();
for (int i = 0; i < mEditors.getChildCount(); ++i) {
Editor editor = (Editor) mEditors.getChildAt(i);
mList.add(editor.needsSaving());
}
return mList;
}
public List<Boolean> getNewKeysArray()
{
ArrayList<Boolean> mList = new ArrayList<Boolean>();
if (mType == Id.type.key) {
for (int i = 0; i < mEditors.getChildCount(); ++i) {
KeyEditor editor = (KeyEditor) mEditors.getChildAt(i);
mList.add(editor.getIsNewKey());
}
}
return mList;
}
/**
* {@inheritDoc}
*/
@ -143,10 +274,11 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
UserIdEditor view = (UserIdEditor) mInflater.inflate(
R.layout.edit_key_user_id_item, mEditors, false);
view.setEditorListener(this);
if (mEditors.getChildCount() == 0) {
view.setIsMainUserId(true);
}
view.setValue("", mEditors.getChildCount() == 0, true);
mEditors.addView(view);
if (mEditorListener != null) {
mEditorListener.onEdited();
}
break;
}
@ -185,10 +317,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
mEditors, false);
view.setEditorListener(this);
view.setValue(userId);
if (mEditors.getChildCount() == 0) {
view.setIsMainUserId(true);
}
view.setValue(userId, mEditors.getChildCount() == 0, false);
view.setCanEdit(mCanEdit);
mEditors.addView(view);
}
@ -196,7 +325,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
this.updateEditorsVisible();
}
public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages) {
public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages, boolean newKeys) {
if (mType != Id.type.key) {
return;
}
@ -209,7 +338,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
false);
view.setEditorListener(this);
boolean isMasterKey = (mEditors.getChildCount() == 0);
view.setValue(list.get(i), isMasterKey, usages.get(i));
view.setValue(list.get(i), isMasterKey, usages.get(i), newKeys);
view.setCanEdit(mCanEdit);
mEditors.addView(view);
}
@ -289,8 +418,14 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
mEditors, false);
view.setEditorListener(SectionView.this);
view.setValue(newKey, newKey.isMasterKey(), -1);
int usage = 0;
if (mEditors.getChildCount() == 0)
usage = PGPKeyFlags.CAN_CERTIFY;
view.setValue(newKey, newKey.isMasterKey(), usage, true);
mEditors.addView(view);
SectionView.this.updateEditorsVisible();
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}

View File

@ -16,6 +16,11 @@
package org.sufficientlysecure.keychain.ui.widget;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
@ -26,28 +31,23 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.*;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ContactHelper;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
private EditorListener mEditorListener = null;
private BootstrapButton mDeleteButton;
private RadioButton mIsMainUserId;
private String mOriginalID;
private EditText mName;
private String mOriginalName;
private AutoCompleteTextView mEmail;
private String mOriginalEmail;
private EditText mComment;
public static class NoNameException extends Exception {
static final long serialVersionUID = 0xf812773343L;
public NoNameException(String message) {
super(message);
}
}
private String mOriginalComment;
private boolean mOriginallyMainUserID;
private boolean mIsNewId;
public void setCanEdit(boolean bCanEdit) {
if (!bCanEdit) {
@ -59,14 +59,6 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
}
}
public static class NoEmailException extends Exception {
static final long serialVersionUID = 0xf812773344L;
public NoEmailException(String message) {
super(message);
}
}
public static class InvalidEmailException extends Exception {
static final long serialVersionUID = 0xf812773345L;
@ -83,6 +75,24 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
super(context, attrs);
}
TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s)
{
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
};
@Override
protected void onFinishInflate() {
setDrawingCacheEnabled(true);
@ -94,8 +104,10 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
mIsMainUserId.setOnClickListener(this);
mName = (EditText) findViewById(R.id.name);
mName.addTextChangedListener(mTextWatcher);
mEmail = (AutoCompleteTextView) findViewById(R.id.email);
mComment = (EditText) findViewById(R.id.comment);
mComment.addTextChangedListener(mTextWatcher);
mEmail.setThreshold(1); // Start working from first character
@ -127,36 +139,45 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
// remove drawable if email is empty
mEmail.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
});
super.onFinishInflate();
}
public void setValue(String userId) {
public void setValue(String userId, boolean isMainID, boolean isNewId) {
mName.setText("");
mOriginalName = "";
mComment.setText("");
mOriginalComment = "";
mEmail.setText("");
mOriginalEmail = "";
mIsNewId = isNewId;
mOriginalID = userId;
Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$");
Matcher matcher = withComment.matcher(userId);
if (matcher.matches()) {
mName.setText(matcher.group(1));
mComment.setText(matcher.group(2));
mEmail.setText(matcher.group(3));
return;
String[] result = PgpKeyHelper.splitUserId(userId);
if (result[0] != null) {
mName.setText(result[0]);
mOriginalName = result[0];
}
if (result[1] != null) {
mEmail.setText(result[1]);
mOriginalEmail = result[1];
}
if (result[2] != null) {
mComment.setText(result[2]);
mOriginalComment = result[2];
}
Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
matcher = withoutComment.matcher(userId);
if (matcher.matches()) {
mName.setText(matcher.group(1));
mEmail.setText(matcher.group(2));
return;
}
mOriginallyMainUserID = isMainID;
setIsMainUserId(isMainID);
}
public String getValue() throws NoNameException, NoEmailException {
public String getValue() {
String name = ("" + mName.getText()).trim();
String email = ("" + mEmail.getText()).trim();
String comment = ("" + mComment.getText()).trim();
@ -173,16 +194,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
// ok, empty one...
return userId;
}
// otherwise make sure that name and email exist
if (name.equals("")) {
throw new NoNameException("need a name");
}
if (email.equals("")) {
throw new NoEmailException("need an email");
}
//TODO: check gpg accepts an entirely empty ID packet. specs say this is allowed
return userId;
}
@ -192,7 +204,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
boolean wasMainUserId = mIsMainUserId.isChecked();
parent.removeView(this);
if (mEditorListener != null) {
mEditorListener.onDeleted(this);
mEditorListener.onDeleted(this, mIsNewId);
}
if (wasMainUserId && parent.getChildCount() > 0) {
UserIdEditor editor = (UserIdEditor) parent.getChildAt(0);
@ -207,6 +219,9 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
editor.setIsMainUserId(false);
}
}
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
}
@ -221,4 +236,29 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
public void setEditorListener(EditorListener listener) {
mEditorListener = listener;
}
@Override
public boolean needsSaving() {
boolean retval = false; //(mOriginallyMainUserID != isMainUserId());
retval |= !(mOriginalName.equals( ("" + mName.getText()).trim() ) );
retval |= !(mOriginalEmail.equals( ("" + mEmail.getText()).trim() ) );
retval |= !(mOriginalComment.equals( ("" + mComment.getText()).trim() ) );
retval |= mIsNewId;
return retval;
}
public boolean getIsOriginallyMainUserID()
{
return mOriginallyMainUserID;
}
public boolean primarySwapped()
{
return (mOriginallyMainUserID != isMainUserId());
}
public String getOriginalID()
{
return mOriginalID;
}
}

View File

@ -11,6 +11,7 @@
android:orientation="horizontal" >
<TableLayout
android:id="@+id/table_keylayout"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
@ -88,7 +89,8 @@
bootstrapbutton:bb_type="default" />
</TableRow>
<TableRow>
<TableRow
android:id="@+id/row_certify">
<TextView
android:id="@+id/label_usage"
@ -97,11 +99,59 @@
android:layout_gravity="center_vertical"
android:paddingRight="10dip"
android:text="@string/label_usage" />
<CheckBox
android:id="@+id/chkCertify"
android:enabled = "false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_certify" />
</TableRow>
<Spinner
android:id="@+id/usage"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TableRow
android:id="@+id/row_sign">
<TextView
android:id="@+id/label_usage2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip"
android:text="@string/label_usage" />
<CheckBox
android:id="@+id/chkSign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_sign" />
</TableRow>
<TableRow
android:id="@+id/row_encrypt">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip" />
<CheckBox
android:id="@+id/chkEncrypt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_encrypt" />
</TableRow>
<TableRow
android:id="@+id/row_authenticate">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="10dip" />
<CheckBox
android:id="@+id/chkAuthenticate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/flag_authenticate" />
</TableRow>
</TableLayout>
@ -122,4 +172,5 @@
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
</org.sufficientlysecure.keychain.ui.widget.KeyEditor>
</org.sufficientlysecure.keychain.ui.widget.KeyEditor>

View File

@ -2,6 +2,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_key_edit_save"
android:icon="@drawable/ic_action_save"
app:showAsAction="always"
android:title="@string/btn_save">
</item>
<item
android:id="@+id/menu_key_edit_export_file"
app:showAsAction="never"

View File

@ -58,8 +58,8 @@
<string name="btn_delete">Delete</string>
<string name="btn_no_date">None</string>
<string name="btn_okay">Okay</string>
<string name="btn_change_passphrase">Change Passphrase</string>
<string name="btn_set_passphrase">Set Passphrase</string>
<string name="btn_change_passphrase">Change New Passphrase</string>
<string name="btn_set_passphrase">Set New Passphrase</string>
<string name="btn_search">Search</string>
<string name="btn_export_to_server">Upload To Keyserver</string>
<string name="btn_next">Next</string>
@ -172,9 +172,6 @@
<!-- choice -->
<string name="choice_none">None</string>
<string name="choice_sign_only">Sign only</string>
<string name="choice_encrypt_only">Encrypt only</string>
<string name="choice_sign_and_encrypt">Sign and Encrypt</string>
<string name="choice_15secs">15 secs</string>
<string name="choice_1min">1 min</string>
<string name="choice_3mins">3 mins</string>
@ -195,6 +192,12 @@
<string name="error">Error</string>
<string name="error_message">Error: %s</string>
<!-- key flags -->
<string name="flag_certify">Certify</string>
<string name="flag_sign">Sign</string>
<string name="flag_encrypt">Encrypt</string>
<string name="flag_authenticate">Authenticate</string>
<!-- sentences -->
<string name="wrong_passphrase">Wrong passphrase.</string>
<string name="using_clipboard_content">Using clipboard content.</string>
@ -220,6 +223,7 @@
<string name="key_deletion_confirmation">Do you really want to delete the key \'%s\'?\nYou can\'t undo this!</string>
<string name="key_deletion_confirmation_multi">Do you really want to delete all selected keys?\nYou can\'t undo this!</string>
<string name="secret_key_deletion_confirmation">Do you really want to delete the SECRET key \'%s\'?\nYou can\'t undo this!</string>
<string name="ask_save_changed_key">You have made changes to the keyring, would you like to save it?</string>
<string name="public_key_deletetion_confirmation">Do you really want to delete the PUBLIC key \'%s\'?\nYou can\'t undo this!</string>
<string name="secret_key_delete_text">Delete Secret Keys ?</string>
<string name="also_export_secret_keys">Also export secret keys?</string>
@ -306,6 +310,7 @@
<string name="error_nfc_needed">NFC is not available on your device!</string>
<string name="error_nothing_import">Nothing to import!</string>
<string name="error_expiry_must_come_after_creation">expiry date must come after creation date</string>
<string name="error_save_first">please save the keyring first</string>
<string name="error_can_not_delete_contact">you can not delete this contact because it is your own.</string>
<string name="error_can_not_delete_contacts">you can not delete the following contacts because they are your own:\n%s</string>
<string name="error_keyserver_insufficient_query">Insufficient server query</string>