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