mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-30 12:32:17 -05:00
Merge pull request #491 from openpgp-keychain/edit-key
Edit key changes
This commit is contained in:
commit
6b5d60e1f4
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user