Merge branch 'master' of github.com:openpgp-keychain/openpgp-keychain

This commit is contained in:
Dominik Schürmann 2014-03-30 19:18:41 +02:00
commit fc4f9beb6a
16 changed files with 1303 additions and 528 deletions

View File

@ -59,13 +59,30 @@ public class PgpConversionHelper {
* @return * @return
*/ */
public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) { 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>(); ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
try {
@SuppressWarnings("unchecked") while ((obj = factory.nextObject()) != null) {
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys(); PGPSecretKey secKey = null;
while (itr.hasNext()) { if(obj instanceof PGPSecretKey) {
keys.add(itr.next()); 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; return keys;

View File

@ -210,9 +210,8 @@ public class PgpKeyHelper {
Calendar calendar = GregorianCalendar.getInstance(); Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(creationDate); calendar.setTime(creationDate);
calendar.add(Calendar.DATE, key.getValidDays()); calendar.add(Calendar.DATE, key.getValidDays());
Date expiryDate = calendar.getTime();
return expiryDate; return calendar.getTime();
} }
public static Date getExpiryDate(PGPSecretKey key) { public static Date getExpiryDate(PGPSecretKey key) {
@ -292,6 +291,28 @@ public class PgpKeyHelper {
return userId; 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") @SuppressWarnings("unchecked")
public static boolean isEncryptionKey(PGPPublicKey key) { public static boolean isEncryptionKey(PGPPublicKey key) {
if (!key.isEncryptionKey()) { if (!key.isEncryptionKey()) {
@ -398,6 +419,36 @@ public class PgpKeyHelper {
return false; 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) { public static boolean isCertificationKey(PGPSecretKey key) {
return isCertificationKey(key.getPublicKey()); return isCertificationKey(key.getPublicKey());
} }
@ -411,7 +462,7 @@ public class PgpKeyHelper {
} }
public static String getAlgorithmInfo(int algorithm, int keySize) { public static String getAlgorithmInfo(int algorithm, int keySize) {
String algorithmStr = null; String algorithmStr;
switch (algorithm) { switch (algorithm) {
case PGPPublicKey.RSA_ENCRYPT: case PGPPublicKey.RSA_ENCRYPT:

View File

@ -17,12 +17,24 @@
package org.sufficientlysecure.keychain.pgp; 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 android.content.Context;
import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.jce.spec.ElGamalParameterSpec; import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.*; import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.PGPUtil;
@ -36,6 +48,12 @@ import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Primes; import org.sufficientlysecure.keychain.util.Primes;
@ -52,8 +70,8 @@ import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
public class PgpKeyOperation { public class PgpKeyOperation {
private Context mContext; private final Context mContext;
private ProgressDialogUpdater mProgress; private final ProgressDialogUpdater mProgress;
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{ private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
@ -71,34 +89,18 @@ public class PgpKeyOperation {
this.mProgress = progress; this.mProgress = progress;
} }
public void updateProgress(int message, int current, int total) { void updateProgress(int message, int current, int total) {
if (mProgress != null) { if (mProgress != null) {
mProgress.setProgress(message, current, total); mProgress.setProgress(message, current, total);
} }
} }
public void updateProgress(int current, int total) { void updateProgress(int current, int total) {
if (mProgress != null) { if (mProgress != null) {
mProgress.setProgress(current, total); 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, public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) boolean isMasterKey)
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
@ -112,8 +114,8 @@ public class PgpKeyOperation {
passphrase = ""; passphrase = "";
} }
int algorithm = 0; int algorithm;
KeyPairGenerator keyGen = null; KeyPairGenerator keyGen;
switch (algorithmChoice) { switch (algorithmChoice) {
case Id.choice.algorithm.dsa: { case Id.choice.algorithm.dsa: {
@ -165,15 +167,12 @@ public class PgpKeyOperation {
PGPEncryptedData.CAST5, sha1Calc) PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor); sha1Calc, isMasterKey, keyEncryptor);
return secKey;
} }
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
String newPassPhrase) throws IOException, PGPException, String newPassPhrase) throws IOException, PGPException {
NoSuchProviderException {
updateProgress(R.string.progress_building_key, 0, 100); updateProgress(R.string.progress_building_key, 0, 100);
if (oldPassPhrase == null) { if (oldPassPhrase == null) {
@ -199,160 +198,82 @@ public class PgpKeyOperation {
} }
public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys, private void buildNewSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys, ArrayList<GregorianCalendar> keysExpiryDates, ArrayList<Integer> keysUsages, String newPassPhrase, String oldPassPhrase) throws PgpGeneralException,
ArrayList<Integer> keysUsages, ArrayList<GregorianCalendar> keysExpiryDates, PGPException, SignatureException, IOException {
PGPPublicKey oldPublicKey, String oldPassPhrase,
String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
PGPException, NoSuchAlgorithmException, 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) { // this removes all userIds and certifications previously attached to the masterPublicKey
oldPassPhrase = ""; PGPPublicKey masterPublicKey = masterKey.getPublicKey();
}
if (newPassPhrase == null) { PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID());
newPassPhrase = "";
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 PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPKeyRingGenerator keyGen; PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPPublicKey masterPublicKey; {
String mainUserId = userIds.get(0); hashedPacketsGen.setKeyFlags(true, usageId);
// prepare the master key pair hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
PGPKeyPair masterKeyPair; { hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
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);
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); updateProgress(R.string.progress_adding_sub_keys, 40, 100);
for (int i = 1; i < keys.size(); ++i) { for (int i = 1; i < keys.size(); ++i) {
@ -361,27 +282,21 @@ public class PgpKeyOperation {
PGPSecretKey subKey = keys.get(i); PGPSecretKey subKey = keys.get(i);
PGPPublicKey subPublicKey = subKey.getPublicKey(); PGPPublicKey subPublicKey = subKey.getPublicKey();
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
oldPassPhrase.toCharArray()); oldPassPhrase.toCharArray());
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor); PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
// TODO: now used without algorithm and creation time?! (APG 1) // TODO: now used without algorithm and creation time?! (APG 1)
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey); PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); hashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
int keyFlags = 0; usageId = keysUsages.get(i);
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
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);
if (canSign) { if (canSign) {
Date todayDate = new Date(); //both sig times the same Date todayDate = new Date(); //both sig times the same
keyFlags |= KeyFlags.SIGN_DATA;
// cross-certify signing keys // cross-certify signing keys
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
@ -396,10 +311,7 @@ public class PgpKeyOperation {
subPublicKey); subPublicKey);
unhashedPacketsGen.setEmbeddedSignature(false, certification); unhashedPacketsGen.setEmbeddedSignature(false, certification);
} }
if (canEncrypt) { hashedPacketsGen.setKeyFlags(false, usageId);
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
}
hashedPacketsGen.setKeyFlags(false, keyFlags);
if (keysExpiryDates.get(i) != null) { if (keysExpiryDates.get(i) != null) {
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
@ -407,16 +319,12 @@ public class PgpKeyOperation {
GregorianCalendar expiryDate = keysExpiryDates.get(i); GregorianCalendar expiryDate = keysExpiryDates.get(i);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c //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! //here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays = long numDays = (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
(expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); if (numDays <= 0)
if (numDays <= 0) { throw new PgpGeneralException(mContext.getString(R.string.error_expiry_must_come_after_creation));
throw new PgpGeneralException
(mContext.getString(R.string.error_expiry_must_come_after_creation));
}
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else { } else {
//do this explicitly, although since we're rebuilding, hashedPacketsGen.setKeyExpirationTime(false, 0); //do this explicitly, although since we're rebuilding,
hashedPacketsGen.setKeyExpirationTime(false, 0);
//this happens anyway //this happens anyway
} }
@ -426,53 +334,339 @@ public class PgpKeyOperation {
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); 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); 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, secretKeyRing);
ProviderHelper.saveKeyRing(mContext, publicKeyRing); ProviderHelper.saveKeyRing(mContext, publicKeyRing);
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
} }
/** public void buildSecretKey (SaveKeyringParcel saveParcel)throws PgpGeneralException,
* Certify the given pubkeyid with the given masterkeyid. PGPException, SignatureException, IOException {
*
* @param masterKeyId Certifying key, must be available as secret key updateProgress(R.string.progress_building_key, 0, 100);
* @param pubKeyId ID of public key to certify PGPSecretKey masterKey = saveParcel.keys.get(0);
* @param userIds User IDs to certify, must not be null or empty
* @param passphrase Passphrase of the secret key PGPSecretKeyRing mKR = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, masterKey.getKeyID());
* @return A keyring with added certifications 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) public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException, throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException { PGPException, SignatureException {

View File

@ -24,7 +24,12 @@ import android.net.Uri;
import android.os.RemoteException; import android.os.RemoteException;
import org.spongycastle.bcpg.ArmoredOutputStream; 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.Constants;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper; 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 * 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)); 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 * 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[]{ String[] projection = new String[]{
KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID,
"(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = " + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._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",}; + " = 1) AS sign",};
ContentResolver cr = context.getContentResolver(); ContentResolver cr = context.getContentResolver();
@ -904,4 +910,4 @@ public class ProviderHelper {
return signature; return signature;
} }
} }

View File

@ -105,15 +105,10 @@ public class KeychainIntentService extends IntentService
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric"; public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
// save keyring // save keyring
public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase"; public static final String SAVE_KEYRING_PARCEL = "save_parcel";
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_CAN_SIGN = "can_sign"; public static final String SAVE_KEYRING_CAN_SIGN = "can_sign";
// generate key // generate key
public static final String GENERATE_KEY_ALGORITHM = "algorithm"; public static final String GENERATE_KEY_ALGORITHM = "algorithm";
public static final String GENERATE_KEY_KEY_SIZE = "key_size"; 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)) { } else if (ACTION_SAVE_KEYRING.equals(action)) {
try { try {
/* Input */ /* Input */
String oldPassPhrase = data.getString(SAVE_KEYRING_CURRENT_PASSPHRASE); SaveKeyringParcel saveParams = data.getParcelable(SAVE_KEYRING_PARCEL);
String newPassPhrase = data.getString(SAVE_KEYRING_NEW_PASSPHRASE); String oldPassPhrase = saveParams.oldPassPhrase;
String newPassPhrase = saveParams.newPassPhrase;
boolean canSign = true; boolean canSign = true;
if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) { if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
@ -527,14 +523,8 @@ public class KeychainIntentService extends IntentService
if (newPassPhrase == null) { if (newPassPhrase == null) {
newPassPhrase = oldPassPhrase; 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); PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
/* Operation */ /* Operation */
@ -543,17 +533,7 @@ public class KeychainIntentService extends IntentService
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId), ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
oldPassPhrase, newPassPhrase); oldPassPhrase, newPassPhrase);
} else { } else {
//TODO: Workaround due to ProviderHelper.getPGPPublicKeyByKeyId can not resolve public key of master-key id with uri/cursor keyOperations.buildSecretKey(saveParams);
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);
} }
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase); PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);

View File

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

View File

@ -17,6 +17,12 @@
package org.sufficientlysecure.keychain.ui; 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.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
@ -41,7 +47,6 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; 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.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; 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.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; 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.KeyEditor;
import org.sufficientlysecure.keychain.ui.widget.SectionView; import org.sufficientlysecure.keychain.ui.widget.SectionView;
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor; 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.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; 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: // Actions for internal use only:
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY"; 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 mNewPassPhrase = null;
private String mSavedNewPassPhrase = null; private String mSavedNewPassPhrase = null;
private boolean mIsPassPhraseSet; private boolean mIsPassPhraseSet;
private boolean mNeedsSaving;
private boolean mIsBrandNewKeyring = false;
private MenuItem mSaveButton;
private BootstrapButton mChangePassphrase; private BootstrapButton mChangePassphrase;
@ -102,12 +118,42 @@ public class EditKeyActivity extends ActionBarActivity {
ExportHelper mExportHelper; 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mExportHelper = new ExportHelper(this); mExportHelper = new ExportHelper(this);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setIcon(android.R.color.transparent);
getSupportActionBar().setHomeButtonEnabled(true);
mUserIds = new Vector<String>(); mUserIds = new Vector<String>();
mKeys = new Vector<PGPSecretKey>(); mKeys = new Vector<PGPSecretKey>();
mKeysUsages = new Vector<Integer>(); mKeysUsages = new Vector<Integer>();
@ -128,24 +174,10 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent * @param intent
*/ */
private void handleActionCreateKey(Intent 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(); Bundle extras = intent.getExtras();
mCurrentPassphrase = ""; mCurrentPassphrase = "";
mIsBrandNewKeyring = true;
if (extras != null) { if (extras != null) {
// if userId is given, prefill the fields // if userId is given, prefill the fields
@ -203,22 +235,24 @@ public class EditKeyActivity extends ActionBarActivity {
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get new key from data bundle returned from service // get new key from data bundle returned from service
Bundle data = message.getData(); Bundle data = message.getData();
PGPSecretKey masterKey = (PGPSecretKey) PgpConversionHelper PGPSecretKey masterKey = PgpConversionHelper
.BytesToPGPSecretKey(data .BytesToPGPSecretKey(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY)); .getByteArray(KeychainIntentService.RESULT_NEW_KEY));
PGPSecretKey subKey = (PGPSecretKey) PgpConversionHelper PGPSecretKey subKey = PgpConversionHelper
.BytesToPGPSecretKey(data .BytesToPGPSecretKey(data
.getByteArray(KeychainIntentService.RESULT_NEW_KEY2)); .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 // add master key
mKeys.add(masterKey); mKeys.add(masterKey);
mKeysUsages.add(Id.choice.usage.sign_only); //TODO: get from key flags mKeysUsages.add(KeyFlags.CERTIFY_OTHER);
// add sub key // add sub key
mKeys.add(subKey); 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 { } else {
buildLayout(); buildLayout(false);
} }
} }
@ -244,40 +278,29 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent * @param intent
*/ */
private void handleActionEditKey(Intent 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(); mDataUri = intent.getData();
if (mDataUri == null) { if (mDataUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
finish(); finish();
return;
} else { } else {
Log.d(Constants.TAG, "uri: " + mDataUri); Log.d(Constants.TAG, "uri: " + mDataUri);
// get master key id using row id // get master key id using row id
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
mMasterCanSign = ProviderHelper.getMasterKeyCanSign(this, mDataUri); mMasterCanSign = ProviderHelper.getMasterKeyCanCertify(this, mDataUri);
finallyEdit(masterKeyId, mMasterCanSign); finallyEdit(masterKeyId);
} }
} }
private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) { private void showPassphraseDialog(final long masterKeyId) {
// Message is received after passphrase is cached // Message is received after passphrase is cached
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
String passphrase = PassphraseCacheService.getCachedPassphrase( mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
EditKeyActivity.this, masterKeyId); EditKeyActivity.this, masterKeyId);
mCurrentPassphrase = passphrase;
finallySaveClicked(); 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 @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.key_edit, 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; return true;
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_key_edit_cancel: case android.R.id.home:
cancelClicked(); 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; return true;
case R.id.menu_key_edit_export_file: 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 masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
long[] ids = new long[]{masterKeyId}; long[] ids = new long[]{masterKeyId};
mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC, mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC,
null); null);
return true; 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); return super.onOptionsItemSelected(item);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void finallyEdit(final long masterKeyId, final boolean masterCanSign) { private void finallyEdit(final long masterKeyId) {
if (masterKeyId != 0) { if (masterKeyId != 0) {
PGPSecretKey masterKey = null; PGPSecretKey masterKey = null;
mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId); 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(); Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
} }
if (masterKey != null) { if (masterKey != null) {
boolean isSet = false;
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) { for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
Log.d(Constants.TAG, "Added userId " + userId); 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); mUserIds.add(userId);
} }
} }
} }
mCurrentPassphrase = ""; mCurrentPassphrase = "";
buildLayout(false);
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
buildLayout();
if (!mIsPassPhraseSet) { if (!mIsPassPhraseSet) {
// check "no passphrase" checkbox and remove button // check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true); mNoPassphrase.setChecked(true);
@ -399,6 +436,7 @@ public class EditKeyActivity extends ActionBarActivity {
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE); .getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
updatePassPhraseButtonText(); updatePassPhraseButtonText();
somethingChanged();
} }
} }
}; };
@ -407,7 +445,7 @@ public class EditKeyActivity extends ActionBarActivity {
Messenger messenger = new Messenger(returnHandler); Messenger messenger = new Messenger(returnHandler);
// set title based on isPassphraseSet() // set title based on isPassphraseSet()
int title = -1; int title;
if (isPassphraseSet()) { if (isPassphraseSet()) {
title = R.string.title_change_passphrase; title = R.string.title_change_passphrase;
} else { } 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 * Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
* id and key. * id and key.
* @param newKeys
*/ */
private void buildLayout() { private void buildLayout(boolean newKeys) {
setContentView(R.layout.edit_key_activity); setContentView(R.layout.edit_key_activity);
// find views // find views
@ -442,11 +481,13 @@ public class EditKeyActivity extends ActionBarActivity {
mUserIdsView.setType(Id.type.user_id); mUserIdsView.setType(Id.type.user_id);
mUserIdsView.setCanEdit(mMasterCanSign); mUserIdsView.setCanEdit(mMasterCanSign);
mUserIdsView.setUserIds(mUserIds); mUserIdsView.setUserIds(mUserIds);
mUserIdsView.setEditorListener(this);
container.addView(mUserIdsView); container.addView(mUserIdsView);
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mKeysView.setType(Id.type.key); mKeysView.setType(Id.type.key);
mKeysView.setCanEdit(mMasterCanSign); mKeysView.setCanEdit(mMasterCanSign);
mKeysView.setKeys(mKeys, mKeysUsages); mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
mKeysView.setEditorListener(this);
container.addView(mKeysView); container.addView(mKeysView);
updatePassPhraseButtonText(); 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() { mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override @Override
@ -471,6 +512,7 @@ public class EditKeyActivity extends ActionBarActivity {
mNewPassPhrase = mSavedNewPassPhrase; mNewPassPhrase = mSavedNewPassPhrase;
mChangePassphrase.setVisibility(View.VISIBLE); 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() { private void saveClicked() {
long masterKeyId = getMasterKeyId(); long masterKeyId = getMasterKeyId();
if (!isPassphraseSet()) { if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
Log.e(Constants.TAG, "No passphrase has been set"); try {
Toast.makeText(this, R.string.set_a_passphrase, Toast.LENGTH_LONG).show(); if (!isPassphraseSet()) {
} else { throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
String passphrase = null; }
if (mIsPassPhraseSet) {
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); String passphrase;
} else { if (mIsPassPhraseSet)
passphrase = ""; passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
} else
if (passphrase == null) { passphrase = "";
showPassphraseDialog(masterKeyId, mMasterCanSign); if (passphrase == null) {
} else { showPassphraseDialog(masterKeyId);
mCurrentPassphrase = passphrase; } else {
finallySaveClicked(); 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() { private void finallySaveClicked() {
try { try {
// Send all information needed to service to edit key in other thread // 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); 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 // fill values for this action
Bundle data = new Bundle(); 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.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@ -579,8 +652,35 @@ public class EditKeyActivity extends ActionBarActivity {
} }
private void cancelClicked() { private void cancelClicked() {
setResult(RESULT_CANCELED); if (needsSaving()) { //ask if we want to save
finish(); 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; boolean gotMainUserId = false;
for (int i = 0; i < userIdEditors.getChildCount(); ++i) { for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i); UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
String userId = null; String userId;
try { userId = editor.getValue();
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;
}
if (editor.isMainUserId()) { if (editor.isMainUserId()) {
userIds.add(0, userId); userIds.add(0, userId);
@ -698,4 +787,4 @@ public class EditKeyActivity extends ActionBarActivity {
: getString(R.string.btn_set_passphrase)); : getString(R.string.btn_set_passphrase));
} }
} }

View File

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

View File

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

View File

@ -16,6 +16,20 @@
package org.sufficientlysecure.keychain.ui.widget; 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.annotation.TargetApi;
import android.app.DatePickerDialog; import android.app.DatePickerDialog;
import android.app.Dialog; import android.app.Dialog;
@ -26,7 +40,13 @@ import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; 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 com.beardedhen.androidbootstrap.BootstrapButton;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
@ -47,11 +67,30 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
BootstrapButton mDeleteButton; BootstrapButton mDeleteButton;
TextView mAlgorithm; TextView mAlgorithm;
TextView mKeyId; TextView mKeyId;
Spinner mUsage;
TextView mCreationDate; TextView mCreationDate;
BootstrapButton mExpiryDateButton; BootstrapButton mExpiryDateButton;
GregorianCalendar mCreatedDate; GregorianCalendar mCreatedDate;
GregorianCalendar mExpiryDate; 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 int mDatePickerResultCount = 0;
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
@ -61,7 +100,18 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
if (mDatePickerResultCount++ == 0) { if (mDatePickerResultCount++ == 0) {
GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
date.set(year, monthOfYear, dayOfMonth); 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); mKeyId = (TextView) findViewById(R.id.keyId);
mCreationDate = (TextView) findViewById(R.id.creation); mCreationDate = (TextView) findViewById(R.id.creation);
mExpiryDateButton = (BootstrapButton) findViewById(R.id.expiry); 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 = (BootstrapButton) findViewById(R.id.delete);
mDeleteButton.setOnClickListener(this); 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); setExpiryDate(null);
@ -125,6 +171,9 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
// Note: Ignore results after the first one - android sends multiples. // Note: Ignore results after the first one - android sends multiples.
if (mDatePickerResultCount++ == 0) { if (mDatePickerResultCount++ == 0) {
setExpiryDate(null); setExpiryDate(null);
if (mEditorListener != null) {
mEditorListener.onEdited();
}
} }
} }
}); });
@ -155,12 +204,14 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
public void setCanEdit(boolean bCanEdit) { public void setCanEdit(boolean bCanEdit) {
if (!bCanEdit) { if (!bCanEdit) {
mDeleteButton.setVisibility(View.INVISIBLE); mDeleteButton.setVisibility(View.INVISIBLE);
mUsage.setEnabled(false);
mExpiryDateButton.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; mKey = key;
mIsMasterKey = isMasterKey; mIsMasterKey = isMasterKey;
@ -175,47 +226,45 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
Vector<Choice> choices = new Vector<Choice>(); Vector<Choice> choices = new Vector<Choice>();
boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA); boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA);
if (!isElGamalKey) { if (isElGamalKey) {
choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString( mChkSign.setVisibility(View.INVISIBLE);
R.string.choice_sign_only))); TableLayout table = (TableLayout)findViewById(R.id.table_keylayout);
TableRow row = (TableRow)findViewById(R.id.row_sign);
table.removeView(row);
} }
if (!mIsMasterKey && !isDSAKey) { if (isDSAKey) {
choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString( mChkEncrypt.setVisibility(View.INVISIBLE);
R.string.choice_encrypt_only))); TableLayout table = (TableLayout)findViewById(R.id.table_keylayout);
TableRow row = (TableRow)findViewById(R.id.row_encrypt);
table.removeView(row);
} }
if (!isElGamalKey && !isDSAKey) { if (!mIsMasterKey) {
choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( mChkCertify.setVisibility(View.INVISIBLE);
R.string.choice_sign_and_encrypt))); TableLayout table = (TableLayout)findViewById(R.id.table_keylayout);
} TableRow row = (TableRow)findViewById(R.id.row_certify);
table.removeView(row);
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;
}
} else { } else {
// set usage if it is predefined TextView mLabelUsage2= (TextView) findViewById(R.id.label_usage2);
if (usage != -1) { mLabelUsage2.setVisibility(View.INVISIBLE);
selectId = usage;
} else {
selectId = Id.choice.usage.sign_only;
}
} }
for (int i = 0; i < choices.size(); ++i) { int selectId = 0;
if (choices.get(i).getId() == selectId) { mIsNewKey = isNewKey;
mUsage.setSelection(i); if (isNewKey) {
break; 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")); GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
@ -228,6 +277,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
} else { } else {
cal.setTime(PgpKeyHelper.getExpiryDate(key)); cal.setTime(PgpKeyHelper.getExpiryDate(key));
setExpiryDate(cal); setExpiryDate(cal);
mOriginalExpiryDate = cal;
} }
} }
@ -241,7 +291,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
if (v == mDeleteButton) { if (v == mDeleteButton) {
parent.removeView(this); parent.removeView(this);
if (mEditorListener != null) { 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() { public int getUsage() {
return ((Choice) mUsage.getSelectedItem()).getId(); mUsage = (mUsage & ~KeyFlags.CERTIFY_OTHER) | (mChkCertify.isChecked() ? KeyFlags.CERTIFY_OTHER : 0);
mUsage = (mUsage & ~KeyFlags.SIGN_DATA) | (mChkSign.isChecked() ? KeyFlags.SIGN_DATA : 0);
mUsage = (mUsage & ~KeyFlags.ENCRYPT_COMMS) | (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_COMMS : 0);
mUsage = (mUsage & ~KeyFlags.ENCRYPT_STORAGE) | (mChkEncrypt.isChecked() ? KeyFlags.ENCRYPT_STORAGE : 0);
mUsage = (mUsage & ~KeyFlags.AUTHENTICATION) | (mChkAuthenticate.isChecked() ? KeyFlags.AUTHENTICATION : 0);
return mUsage;
}
public boolean needsSaving()
{
if (mIsNewKey)
return true;
boolean retval = (getUsage() != mOriginalUsage);
boolean dateChanged;
boolean mOEDNull = (mOriginalExpiryDate == null);
boolean mEDNull = (mExpiryDate == null);
if (mOEDNull != mEDNull) {
dateChanged = true;
} else {
if(mOEDNull) //both null, no change
dateChanged = false;
else
dateChanged = ((mExpiryDate.compareTo(mOriginalExpiryDate)) != 0);
}
retval |= dateChanged;
return retval;
}
public boolean getIsNewKey()
{
return mIsNewKey;
} }
} }

View File

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

View File

@ -16,6 +16,23 @@
package org.sufficientlysecure.keychain.ui.widget; 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.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; 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.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.util.Choice; 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 LayoutInflater mInflater;
private BootstrapButton mPlusButton; private BootstrapButton mPlusButton;
private ViewGroup mEditors; private ViewGroup mEditors;
private TextView mTitle; private TextView mTitle;
private int mType = 0; private int mType = 0;
private EditorListener mEditorListener = null;
private Choice mNewKeyAlgorithmChoice; private Choice mNewKeyAlgorithmChoice;
private int mNewKeySize; 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 boolean mCanEdit = true;
private ActionBarActivity mActivity; private ActionBarActivity mActivity;
private ProgressDialogFragment mGeneratingDialog; private ProgressDialogFragment mGeneratingDialog;
public void setEditorListener(EditorListener listener) {
mEditorListener = listener;
}
public SectionView(Context context) { public SectionView(Context context) {
super(context); super(context);
mActivity = (ActionBarActivity) context; mActivity = (ActionBarActivity) context;
@ -124,8 +149,26 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
/** /**
* {@inheritDoc} * {@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(); this.updateEditorsVisible();
if (mEditorListener != null) {
mEditorListener.onEdited();
}
}
@Override
public void onEdited() {
if (mEditorListener != null) {
mEditorListener.onEdited();
}
} }
protected void updateEditorsVisible() { protected void updateEditorsVisible() {
@ -133,6 +176,94 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); 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} * {@inheritDoc}
*/ */
@ -143,10 +274,11 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
UserIdEditor view = (UserIdEditor) mInflater.inflate( UserIdEditor view = (UserIdEditor) mInflater.inflate(
R.layout.edit_key_user_id_item, mEditors, false); R.layout.edit_key_user_id_item, mEditors, false);
view.setEditorListener(this); view.setEditorListener(this);
if (mEditors.getChildCount() == 0) { view.setValue("", mEditors.getChildCount() == 0, true);
view.setIsMainUserId(true);
}
mEditors.addView(view); mEditors.addView(view);
if (mEditorListener != null) {
mEditorListener.onEdited();
}
break; 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, UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
mEditors, false); mEditors, false);
view.setEditorListener(this); view.setEditorListener(this);
view.setValue(userId); view.setValue(userId, mEditors.getChildCount() == 0, false);
if (mEditors.getChildCount() == 0) {
view.setIsMainUserId(true);
}
view.setCanEdit(mCanEdit); view.setCanEdit(mCanEdit);
mEditors.addView(view); mEditors.addView(view);
} }
@ -196,7 +325,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
this.updateEditorsVisible(); 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) { if (mType != Id.type.key) {
return; return;
} }
@ -209,7 +338,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
false); false);
view.setEditorListener(this); view.setEditorListener(this);
boolean isMasterKey = (mEditors.getChildCount() == 0); 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); view.setCanEdit(mCanEdit);
mEditors.addView(view); 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, KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
mEditors, false); mEditors, false);
view.setEditorListener(SectionView.this); 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); mEditors.addView(view);
SectionView.this.updateEditorsVisible(); SectionView.this.updateEditorsVisible();
if (mEditorListener != null) {
mEditorListener.onEdited();
}
} }
} }

View File

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

View File

@ -11,6 +11,7 @@
android:orientation="horizontal" > android:orientation="horizontal" >
<TableLayout <TableLayout
android:id="@+id/table_keylayout"
android:layout_width="0dip" android:layout_width="0dip"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
@ -88,7 +89,8 @@
bootstrapbutton:bb_type="default" /> bootstrapbutton:bb_type="default" />
</TableRow> </TableRow>
<TableRow> <TableRow
android:id="@+id/row_certify">
<TextView <TextView
android:id="@+id/label_usage" android:id="@+id/label_usage"
@ -97,11 +99,59 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingRight="10dip" android:paddingRight="10dip"
android:text="@string/label_usage" /> 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 <TableRow
android:id="@+id/usage" android:id="@+id/row_sign">
android:layout_width="match_parent"
android:layout_height="wrap_content" /> <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> </TableRow>
</TableLayout> </TableLayout>
@ -122,4 +172,5 @@
android:layout_height="1dip" android:layout_height="1dip"
android:background="?android:attr/listDivider" /> android:background="?android:attr/listDivider" />
</org.sufficientlysecure.keychain.ui.widget.KeyEditor> </org.sufficientlysecure.keychain.ui.widget.KeyEditor>

View File

@ -2,6 +2,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> 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 <item
android:id="@+id/menu_key_edit_export_file" android:id="@+id/menu_key_edit_export_file"
app:showAsAction="never" app:showAsAction="never"

View File

@ -58,8 +58,8 @@
<string name="btn_delete">Delete</string> <string name="btn_delete">Delete</string>
<string name="btn_no_date">None</string> <string name="btn_no_date">None</string>
<string name="btn_okay">Okay</string> <string name="btn_okay">Okay</string>
<string name="btn_change_passphrase">Change Passphrase</string> <string name="btn_change_passphrase">Change New Passphrase</string>
<string name="btn_set_passphrase">Set Passphrase</string> <string name="btn_set_passphrase">Set New Passphrase</string>
<string name="btn_search">Search</string> <string name="btn_search">Search</string>
<string name="btn_export_to_server">Upload To Keyserver</string> <string name="btn_export_to_server">Upload To Keyserver</string>
<string name="btn_next">Next</string> <string name="btn_next">Next</string>
@ -172,9 +172,6 @@
<!-- choice --> <!-- choice -->
<string name="choice_none">None</string> <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_15secs">15 secs</string>
<string name="choice_1min">1 min</string> <string name="choice_1min">1 min</string>
<string name="choice_3mins">3 mins</string> <string name="choice_3mins">3 mins</string>
@ -195,6 +192,12 @@
<string name="error">Error</string> <string name="error">Error</string>
<string name="error_message">Error: %s</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 --> <!-- sentences -->
<string name="wrong_passphrase">Wrong passphrase.</string> <string name="wrong_passphrase">Wrong passphrase.</string>
<string name="using_clipboard_content">Using clipboard content.</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">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="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="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="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="secret_key_delete_text">Delete Secret Keys ?</string>
<string name="also_export_secret_keys">Also export 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_nfc_needed">NFC is not available on your device!</string>
<string name="error_nothing_import">Nothing to import!</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_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_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_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> <string name="error_keyserver_insufficient_query">Insufficient server query</string>