mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-06 17:25:05 -05:00
Merge branch 'certify' into certs
Conflicts: OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java OpenPGP-Keychain/src/main/res/values/strings.xml
This commit is contained in:
commit
6bacb5ff51
@ -17,7 +17,12 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.helper;
|
package org.sufficientlysecure.keychain.helper;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
@ -60,6 +65,63 @@ public class OtherHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
|
||||||
|
SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
|
||||||
|
try {
|
||||||
|
// for each 4 characters of the fingerprint + 1 space
|
||||||
|
for (int i = 0; i < fingerprint.length(); i += 5) {
|
||||||
|
int spanEnd = Math.min(i + 4, fingerprint.length());
|
||||||
|
String fourChars = fingerprint.substring(i, spanEnd);
|
||||||
|
|
||||||
|
int raw = Integer.parseInt(fourChars, 16);
|
||||||
|
byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)};
|
||||||
|
int[] color = OtherHelper.getRgbForData(bytes);
|
||||||
|
int r = color[0];
|
||||||
|
int g = color[1];
|
||||||
|
int b = color[2];
|
||||||
|
|
||||||
|
// we cannot change black by multiplication, so adjust it to an almost-black grey,
|
||||||
|
// which will then be brightened to the minimal brightness level
|
||||||
|
if (r == 0 && g == 0 && b == 0) {
|
||||||
|
r = 1;
|
||||||
|
g = 1;
|
||||||
|
b = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert rgb to brightness
|
||||||
|
double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||||
|
|
||||||
|
// If a color is too dark to be seen on black,
|
||||||
|
// then brighten it up to a minimal brightness.
|
||||||
|
if (brightness < 80) {
|
||||||
|
double factor = 80.0 / brightness;
|
||||||
|
r = Math.min(255, (int) (r * factor));
|
||||||
|
g = Math.min(255, (int) (g * factor));
|
||||||
|
b = Math.min(255, (int) (b * factor));
|
||||||
|
|
||||||
|
// If it is too light, then darken it to a respective maximal brightness.
|
||||||
|
} else if (brightness > 180) {
|
||||||
|
double factor = 180.0 / brightness;
|
||||||
|
r = (int) (r * factor);
|
||||||
|
g = (int) (g * factor);
|
||||||
|
b = (int) (b * factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a foreground color with the 3 digest integers as RGB
|
||||||
|
// and then converting that int to hex to use as a color
|
||||||
|
sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)),
|
||||||
|
i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(Constants.TAG, "Colorization failed", e);
|
||||||
|
// if anything goes wrong, then just display the fingerprint without colour,
|
||||||
|
// instead of partially correct colour or wrong colours
|
||||||
|
return new SpannableStringBuilder(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given bytes to a unique RGB color using SHA1 algorithm
|
* Converts the given bytes to a unique RGB color using SHA1 algorithm
|
||||||
*
|
*
|
||||||
|
@ -36,6 +36,7 @@ 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.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;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||||
@ -46,6 +47,8 @@ import java.security.*;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
public class PgpKeyOperation {
|
public class PgpKeyOperation {
|
||||||
@ -198,7 +201,7 @@ public class PgpKeyOperation {
|
|||||||
|
|
||||||
public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
||||||
ArrayList<Integer> keysUsages, ArrayList<GregorianCalendar> keysExpiryDates,
|
ArrayList<Integer> keysUsages, ArrayList<GregorianCalendar> keysExpiryDates,
|
||||||
long masterKeyId, String oldPassPhrase,
|
PGPPublicKey oldPublicKey, String oldPassPhrase,
|
||||||
String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
|
String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
|
||||||
PGPException, NoSuchAlgorithmException, SignatureException, IOException {
|
PGPException, NoSuchAlgorithmException, SignatureException, IOException {
|
||||||
|
|
||||||
@ -215,131 +218,166 @@ public class PgpKeyOperation {
|
|||||||
|
|
||||||
updateProgress(R.string.progress_preparing_master_key, 10, 100);
|
updateProgress(R.string.progress_preparing_master_key, 10, 100);
|
||||||
|
|
||||||
int usageId = keysUsages.get(0);
|
// prepare keyring generator with given master public and secret key
|
||||||
boolean canSign =
|
PGPKeyRingGenerator keyGen;
|
||||||
(usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
|
PGPPublicKey masterPublicKey; {
|
||||||
boolean canEncrypt =
|
|
||||||
(usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
|
|
||||||
|
|
||||||
String mainUserId = userIds.get(0);
|
String mainUserId = userIds.get(0);
|
||||||
|
|
||||||
PGPSecretKey masterKey = keys.get(0);
|
// prepare the master key pair
|
||||||
|
PGPKeyPair masterKeyPair; {
|
||||||
|
|
||||||
// this removes all userIds and certifications previously attached to the masterPublicKey
|
PGPSecretKey masterKey = keys.get(0);
|
||||||
PGPPublicKey tmpKey = masterKey.getPublicKey();
|
|
||||||
PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
|
|
||||||
tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
|
|
||||||
|
|
||||||
// already done by code above:
|
// this removes all userIds and certifications previously attached to the masterPublicKey
|
||||||
// PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
PGPPublicKey tmpKey = masterKey.getPublicKey();
|
||||||
// // Somehow, the PGPPublicKey already has an empty certification attached to it when the
|
masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
|
||||||
// // keyRing is generated the first time, we remove that when it exists, before adding the
|
tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
|
||||||
// new
|
|
||||||
// // ones
|
|
||||||
// PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey,
|
|
||||||
// "");
|
|
||||||
// if (masterPublicKeyRmCert != null) {
|
|
||||||
// masterPublicKey = masterPublicKeyRmCert;
|
|
||||||
// }
|
|
||||||
|
|
||||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
// already done by code above:
|
||||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
|
// PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
||||||
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
// // 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;
|
||||||
|
// }
|
||||||
|
|
||||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
|
||||||
|
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
||||||
|
|
||||||
// TODO: if we are editing a key, keep old certs, don't remake certs we don't have to.
|
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||||
|
|
||||||
for (String userId : userIds) {
|
// re-add old certificates, or create new ones for new uids
|
||||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
for (String userId : userIds) {
|
||||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
// re-add certs for this uid, take a note if self-signed cert is in there
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
boolean foundSelfSign = false;
|
||||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
// there was an old self-signed certificate for this uid
|
||||||
|
if(foundSelfSign)
|
||||||
|
continue;
|
||||||
|
|
||||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
Log.d(Constants.TAG, "generating self-signed cert for " + userId);
|
||||||
|
|
||||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||||
}
|
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||||
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
|
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||||
|
|
||||||
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
|
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||||
|
|
||||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
|
||||||
|
|
||||||
int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA;
|
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
||||||
if (canEncrypt) {
|
}
|
||||||
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
|
||||||
}
|
|
||||||
hashedPacketsGen.setKeyFlags(true, keyFlags);
|
|
||||||
|
|
||||||
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
|
masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
|
||||||
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 {
|
PGPSignatureSubpacketGenerator hashedPacketsGen;
|
||||||
//do this explicitly, although since we're rebuilding,
|
PGPSignatureSubpacketGenerator unhashedPacketsGen; {
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
|
||||||
//this happens anyway
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
updateProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100);
|
updateProgress(40 + 40 * (i - 1) / (keys.size() - 1), 100);
|
||||||
|
|
||||||
PGPSecretKey subKey = keys.get(i);
|
PGPSecretKey subKey = keys.get(i);
|
||||||
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
||||||
|
|
||||||
PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||||
oldPassPhrase.toCharArray());
|
oldPassPhrase.toCharArray());
|
||||||
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
|
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
|
|
||||||
keyFlags = 0;
|
int keyFlags = 0;
|
||||||
|
|
||||||
usageId = keysUsages.get(i);
|
int usageId = keysUsages.get(i);
|
||||||
canSign =
|
boolean canSign =
|
||||||
(usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
|
(usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||||
canEncrypt =
|
boolean canEncrypt =
|
||||||
(usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
|
(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
|
||||||
@ -388,53 +426,99 @@ 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))) {
|
||||||
|
// 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 PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, String passphrase)
|
/**
|
||||||
|
* Certify the given pubkeyid with the given masterkeyid.
|
||||||
|
*
|
||||||
|
* @param masterKeyId Certifying key, must be available as secret key
|
||||||
|
* @param pubKeyId ID of public key to certify
|
||||||
|
* @param userIds User IDs to certify, must not be null or empty
|
||||||
|
* @param passphrase Passphrase of the secret key
|
||||||
|
* @return A keyring with added certifications
|
||||||
|
*/
|
||||||
|
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
|
||||||
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
|
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||||
PGPException, SignatureException {
|
PGPException, SignatureException {
|
||||||
if (passphrase == null) {
|
if (passphrase == null) {
|
||||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||||
|
PGPSignatureGenerator signatureGenerator; {
|
||||||
|
|
||||||
|
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||||
|
if (certificationKey == null) {
|
||||||
|
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||||
|
}
|
||||||
|
|
||||||
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||||
|
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||||
|
if (signaturePrivateKey == null) {
|
||||||
|
throw new PgpGeneralException(
|
||||||
|
mContext.getString(R.string.error_could_not_extract_private_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: SHA256 fixed?
|
||||||
|
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||||
|
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||||
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
|
|
||||||
|
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||||
|
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // supply signatureGenerator with a SubpacketVector
|
||||||
|
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||||
|
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||||
|
signatureGenerator.setHashedSubpackets(packetVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch public key ring, add the certification and return it
|
||||||
PGPPublicKeyRing pubring = ProviderHelper
|
PGPPublicKeyRing pubring = ProviderHelper
|
||||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
||||||
|
PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId);
|
||||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
for(String userId : new IterableIterator<String>(userIds.iterator())) {
|
||||||
if (certificationKey == null) {
|
PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey);
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
signedKey = PGPPublicKey.addCertification(signedKey, userId, sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
|
||||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
|
||||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
|
||||||
if (signaturePrivateKey == null) {
|
|
||||||
throw new PgpGeneralException(
|
|
||||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: SHA256 fixed?
|
|
||||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
|
||||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
|
||||||
|
|
||||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
|
|
||||||
contentSignerBuilder);
|
|
||||||
|
|
||||||
signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey);
|
|
||||||
|
|
||||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
|
||||||
|
|
||||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
|
||||||
signatureGenerator.setHashedSubpackets(packetVector);
|
|
||||||
|
|
||||||
PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId),
|
|
||||||
signatureGenerator.generate());
|
|
||||||
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
||||||
|
|
||||||
return pubring;
|
return pubring;
|
||||||
|
@ -55,6 +55,7 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
private static final int PUBLIC_KEY_RING_USER_ID = 121;
|
private static final int PUBLIC_KEY_RING_USER_ID = 121;
|
||||||
private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122;
|
private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122;
|
||||||
|
private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID = 123;
|
||||||
|
|
||||||
private static final int SECRET_KEY_RING = 201;
|
private static final int SECRET_KEY_RING = 201;
|
||||||
private static final int SECRET_KEY_RING_BY_ROW_ID = 202;
|
private static final int SECRET_KEY_RING_BY_ROW_ID = 202;
|
||||||
@ -157,6 +158,7 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
* <pre>
|
* <pre>
|
||||||
* key_rings/public/#/user_ids
|
* key_rings/public/#/user_ids
|
||||||
* key_rings/public/#/user_ids/#
|
* key_rings/public/#/user_ids/#
|
||||||
|
* key_rings/public/master_key_id/#/user_ids
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
|
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
|
||||||
@ -165,6 +167,10 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
|
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
|
||||||
+ KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
|
+ KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
|
||||||
PUBLIC_KEY_RING_USER_ID_BY_ROW_ID);
|
PUBLIC_KEY_RING_USER_ID_BY_ROW_ID);
|
||||||
|
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
|
||||||
|
+ KeychainContract.PATH_PUBLIC + "/"
|
||||||
|
+ KeychainContract.PATH_BY_MASTER_KEY_ID + "/*/" + KeychainContract.PATH_USER_IDS,
|
||||||
|
PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* secret key rings
|
* secret key rings
|
||||||
@ -311,6 +317,7 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
return Keys.CONTENT_ITEM_TYPE;
|
return Keys.CONTENT_ITEM_TYPE;
|
||||||
|
|
||||||
case PUBLIC_KEY_RING_USER_ID:
|
case PUBLIC_KEY_RING_USER_ID:
|
||||||
|
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:
|
||||||
case SECRET_KEY_RING_USER_ID:
|
case SECRET_KEY_RING_USER_ID:
|
||||||
return UserIds.CONTENT_TYPE;
|
return UserIds.CONTENT_TYPE;
|
||||||
|
|
||||||
@ -348,6 +355,7 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
case PUBLIC_KEY_RING_KEY:
|
case PUBLIC_KEY_RING_KEY:
|
||||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||||
case PUBLIC_KEY_RING_USER_ID:
|
case PUBLIC_KEY_RING_USER_ID:
|
||||||
|
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:
|
||||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||||
type = KeyTypes.PUBLIC;
|
type = KeyTypes.PUBLIC;
|
||||||
break;
|
break;
|
||||||
@ -390,6 +398,11 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
// TODO: deprecated master key id
|
// TODO: deprecated master key id
|
||||||
//projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID);
|
//projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID);
|
||||||
|
|
||||||
|
projectionMap.put(KeysColumns.ALGORITHM, Tables.KEYS + "." + KeysColumns.ALGORITHM);
|
||||||
|
projectionMap.put(KeysColumns.KEY_SIZE, Tables.KEYS + "." + KeysColumns.KEY_SIZE);
|
||||||
|
projectionMap.put(KeysColumns.CREATION, Tables.KEYS + "." + KeysColumns.CREATION);
|
||||||
|
projectionMap.put(KeysColumns.EXPIRY, Tables.KEYS + "." + KeysColumns.EXPIRY);
|
||||||
|
projectionMap.put(KeysColumns.KEY_RING_ROW_ID, Tables.KEYS + "." + KeysColumns.KEY_RING_ROW_ID);
|
||||||
projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT);
|
projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT);
|
||||||
projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED);
|
projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED);
|
||||||
|
|
||||||
@ -429,6 +442,18 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
return projectionMap;
|
return projectionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HashMap<String, String> getProjectionMapForUserIds() {
|
||||||
|
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||||
|
|
||||||
|
projectionMap.put(BaseColumns._ID, Tables.USER_IDS + "." + BaseColumns._ID);
|
||||||
|
projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);
|
||||||
|
projectionMap.put(UserIdsColumns.RANK, Tables.USER_IDS + "." + UserIdsColumns.RANK);
|
||||||
|
projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "."
|
||||||
|
+ KeyRingsColumns.MASTER_KEY_ID);
|
||||||
|
|
||||||
|
return projectionMap;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds default query for keyRings: KeyRings table is joined with UserIds and Keys
|
* Builds default query for keyRings: KeyRings table is joined with UserIds and Keys
|
||||||
*/
|
*/
|
||||||
@ -633,6 +658,17 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:
|
||||||
|
qb.setTables(Tables.USER_IDS + " INNER JOIN " + Tables.KEY_RINGS + " ON " + "("
|
||||||
|
+ Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "."
|
||||||
|
+ KeysColumns.KEY_RING_ROW_ID + " )");
|
||||||
|
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = ");
|
||||||
|
qb.appendWhereEscapeString(uri.getPathSegments().get(3));
|
||||||
|
|
||||||
|
qb.setProjectionMap(getProjectionMapForUserIds());
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case PUBLIC_KEY_RING_USER_ID:
|
case PUBLIC_KEY_RING_USER_ID:
|
||||||
case SECRET_KEY_RING_USER_ID:
|
case SECRET_KEY_RING_USER_ID:
|
||||||
qb.setTables(Tables.USER_IDS
|
qb.setTables(Tables.USER_IDS
|
||||||
|
@ -143,6 +143,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
// sign key
|
// sign key
|
||||||
public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
|
public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
|
||||||
public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
|
public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
|
||||||
|
public static final String CERTIFY_KEY_UIDS = "sign_key_uids";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* possible data keys as result send over messenger
|
* possible data keys as result send over messenger
|
||||||
@ -542,8 +543,9 @@ public class KeychainIntentService extends IntentService
|
|||||||
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
|
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
|
||||||
oldPassPhrase, newPassPhrase);
|
oldPassPhrase, newPassPhrase);
|
||||||
} else {
|
} else {
|
||||||
keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates, masterKeyId,
|
PGPPublicKey pubkey = ProviderHelper.getPGPPublicKeyByKeyId(this, masterKeyId);
|
||||||
oldPassPhrase, newPassPhrase);
|
keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates,
|
||||||
|
pubkey, oldPassPhrase, newPassPhrase);
|
||||||
}
|
}
|
||||||
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
|
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
|
||||||
|
|
||||||
@ -804,6 +806,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
/* Input */
|
/* Input */
|
||||||
long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
|
long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
|
||||||
long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
|
long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
|
||||||
|
ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);
|
||||||
|
|
||||||
/* Operation */
|
/* Operation */
|
||||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||||
@ -811,7 +814,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
||||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
|
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
|
||||||
signaturePassPhrase);
|
userIds, signaturePassPhrase);
|
||||||
|
|
||||||
// store the signed key in our local cache
|
// store the signed key in our local cache
|
||||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||||
|
@ -19,11 +19,15 @@ package org.sufficientlysecure.keychain.ui;
|
|||||||
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -32,26 +36,28 @@ import android.widget.*;
|
|||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.spongycastle.openpgp.PGPSignature;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
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.ui.adapter.ViewKeyUserIdsAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs the specified public key with the specified secret master key
|
* Signs the specified public key with the specified secret master key
|
||||||
*/
|
*/
|
||||||
public class CertifyKeyActivity extends ActionBarActivity implements
|
public class CertifyKeyActivity extends ActionBarActivity implements
|
||||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
|
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
private BootstrapButton mSignButton;
|
private BootstrapButton mSignButton;
|
||||||
private CheckBox mUploadKeyCheckbox;
|
private CheckBox mUploadKeyCheckbox;
|
||||||
private Spinner mSelectKeyserverSpinner;
|
private Spinner mSelectKeyserverSpinner;
|
||||||
@ -62,6 +68,12 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
private long mPubKeyId = 0;
|
private long mPubKeyId = 0;
|
||||||
private long mMasterKeyId = 0;
|
private long mMasterKeyId = 0;
|
||||||
|
|
||||||
|
private ListView mUserIds;
|
||||||
|
private ViewKeyUserIdsAdapter mUserIdsAdapter;
|
||||||
|
|
||||||
|
private static final int LOADER_ID_KEYRING = 0;
|
||||||
|
private static final int LOADER_ID_USER_IDS = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -125,9 +137,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Log.e(Constants.TAG, "uri: " + mDataUri);
|
||||||
|
|
||||||
PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri);
|
PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri);
|
||||||
|
|
||||||
|
mUserIds = (ListView) findViewById(R.id.user_ids);
|
||||||
|
|
||||||
|
mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true);
|
||||||
|
mUserIds.setAdapter(mUserIdsAdapter);
|
||||||
|
|
||||||
|
getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
|
||||||
|
getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||||
|
|
||||||
if (signKey != null) {
|
if (signKey != null) {
|
||||||
mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID();
|
mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID();
|
||||||
}
|
}
|
||||||
@ -138,6 +159,76 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static final String[] KEYRING_PROJECTION =
|
||||||
|
new String[] {
|
||||||
|
KeychainContract.KeyRings._ID,
|
||||||
|
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||||
|
KeychainContract.Keys.FINGERPRINT,
|
||||||
|
KeychainContract.UserIds.USER_ID
|
||||||
|
};
|
||||||
|
static final int INDEX_MASTER_KEY_ID = 1;
|
||||||
|
static final int INDEX_FINGERPRINT = 2;
|
||||||
|
static final int INDEX_USER_ID = 3;
|
||||||
|
|
||||||
|
static final String[] USER_IDS_PROJECTION =
|
||||||
|
new String[]{
|
||||||
|
KeychainContract.UserIds._ID,
|
||||||
|
KeychainContract.UserIds.USER_ID,
|
||||||
|
KeychainContract.UserIds.RANK
|
||||||
|
};
|
||||||
|
static final String USER_IDS_SORT_ORDER =
|
||||||
|
KeychainContract.UserIds.RANK + " ASC";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
switch(id) {
|
||||||
|
case LOADER_ID_KEYRING:
|
||||||
|
return new CursorLoader(this, mDataUri, KEYRING_PROJECTION, null, null, null);
|
||||||
|
case LOADER_ID_USER_IDS: {
|
||||||
|
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
|
||||||
|
return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
|
switch(loader.getId()) {
|
||||||
|
case LOADER_ID_KEYRING:
|
||||||
|
// the first key here is our master key
|
||||||
|
if (data.moveToFirst()) {
|
||||||
|
long keyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||||
|
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||||
|
((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
|
||||||
|
|
||||||
|
String mainUserId = data.getString(INDEX_USER_ID);
|
||||||
|
((TextView) findViewById(R.id.main_user_id)).setText(mainUserId);
|
||||||
|
|
||||||
|
byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT);
|
||||||
|
if (fingerprintBlob == null) {
|
||||||
|
// FALLBACK for old database entries
|
||||||
|
fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri);
|
||||||
|
}
|
||||||
|
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
|
||||||
|
((TextView) findViewById(R.id.fingerprint)).setText(OtherHelper.colorizeFingerprint(fingerprint));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LOADER_ID_USER_IDS:
|
||||||
|
mUserIdsAdapter.swapCursor(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
switch(loader.getId()) {
|
||||||
|
case LOADER_ID_USER_IDS:
|
||||||
|
mUserIdsAdapter.swapCursor(null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void showPassphraseDialog(final long secretKeyId) {
|
private void showPassphraseDialog(final long secretKeyId) {
|
||||||
// Message is received after passphrase is cached
|
// Message is received after passphrase is cached
|
||||||
Handler returnHandler = new Handler() {
|
Handler returnHandler = new Handler() {
|
||||||
@ -173,6 +264,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
// if we have already signed this key, dont bother doing it again
|
// if we have already signed this key, dont bother doing it again
|
||||||
boolean alreadySigned = false;
|
boolean alreadySigned = false;
|
||||||
|
|
||||||
|
/* todo: reconsider this at a later point when certs are in the db
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures();
|
Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures();
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
@ -182,6 +274,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if (!alreadySigned) {
|
if (!alreadySigned) {
|
||||||
/*
|
/*
|
||||||
@ -209,6 +302,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
* kicks off the actual signing process on a background thread
|
* kicks off the actual signing process on a background thread
|
||||||
*/
|
*/
|
||||||
private void startSigning() {
|
private void startSigning() {
|
||||||
|
|
||||||
|
// Bail out if there is not at least one user id selected
|
||||||
|
ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
|
||||||
|
if(userIds.isEmpty()) {
|
||||||
|
Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Send all information needed to service to sign key in other thread
|
// Send all information needed to service to sign key in other thread
|
||||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||||
|
|
||||||
@ -219,6 +321,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
|
|
||||||
data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);
|
data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);
|
||||||
data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId);
|
data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId);
|
||||||
|
data.putStringArrayList(KeychainIntentService.CERTIFY_KEY_UIDS, userIds);
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
|||||||
private TextView mKeyUserId;
|
private TextView mKeyUserId;
|
||||||
private TextView mKeyUserIdRest;
|
private TextView mKeyUserIdRest;
|
||||||
private TextView mKeyMasterKeyIdHex;
|
private TextView mKeyMasterKeyIdHex;
|
||||||
|
private TextView mNoKeySelected;
|
||||||
private BootstrapButton mSelectKeyButton;
|
private BootstrapButton mSelectKeyButton;
|
||||||
private Boolean mFilterCertify;
|
private Boolean mFilterCertify;
|
||||||
|
|
||||||
@ -60,10 +61,10 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
|||||||
|
|
||||||
public void selectKey(long secretKeyId) {
|
public void selectKey(long secretKeyId) {
|
||||||
if (secretKeyId == Id.key.none) {
|
if (secretKeyId == Id.key.none) {
|
||||||
mKeyMasterKeyIdHex.setText(R.string.api_settings_no_key);
|
mNoKeySelected.setVisibility(View.VISIBLE);
|
||||||
mKeyUserIdRest.setText("");
|
|
||||||
mKeyUserId.setVisibility(View.GONE);
|
mKeyUserId.setVisibility(View.GONE);
|
||||||
mKeyUserIdRest.setVisibility(View.GONE);
|
mKeyUserIdRest.setVisibility(View.GONE);
|
||||||
|
mKeyMasterKeyIdHex.setVisibility(View.GONE);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
|
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
|
||||||
@ -93,20 +94,25 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
|||||||
mKeyMasterKeyIdHex.setText(masterkeyIdHex);
|
mKeyMasterKeyIdHex.setText(masterkeyIdHex);
|
||||||
mKeyUserId.setText(userName);
|
mKeyUserId.setText(userName);
|
||||||
mKeyUserIdRest.setText(userEmail);
|
mKeyUserIdRest.setText(userEmail);
|
||||||
|
mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);
|
||||||
mKeyUserId.setVisibility(View.VISIBLE);
|
mKeyUserId.setVisibility(View.VISIBLE);
|
||||||
mKeyUserIdRest.setVisibility(View.VISIBLE);
|
mKeyUserIdRest.setVisibility(View.VISIBLE);
|
||||||
|
mNoKeySelected.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
mKeyMasterKeyIdHex.setText(getActivity().getResources().getString(R.string.no_key));
|
mKeyMasterKeyIdHex.setVisibility(View.GONE);
|
||||||
mKeyUserId.setVisibility(View.GONE);
|
mKeyUserId.setVisibility(View.GONE);
|
||||||
mKeyUserIdRest.setVisibility(View.GONE);
|
mKeyUserIdRest.setVisibility(View.GONE);
|
||||||
|
mNoKeySelected.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mKeyMasterKeyIdHex.setText(
|
mKeyMasterKeyIdHex.setText(
|
||||||
getActivity().getResources()
|
getActivity().getResources()
|
||||||
.getString(R.string.no_keys_added_or_updated)
|
.getString(R.string.no_keys_added_or_updated)
|
||||||
+ " for master id: " + secretKeyId);
|
+ " for master id: " + secretKeyId);
|
||||||
|
mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);
|
||||||
mKeyUserId.setVisibility(View.GONE);
|
mKeyUserId.setVisibility(View.GONE);
|
||||||
mKeyUserIdRest.setVisibility(View.GONE);
|
mKeyUserIdRest.setVisibility(View.GONE);
|
||||||
|
mNoKeySelected.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -124,6 +130,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
|||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.select_secret_key_layout_fragment, container, false);
|
View view = inflater.inflate(R.layout.select_secret_key_layout_fragment, container, false);
|
||||||
|
|
||||||
|
mNoKeySelected = (TextView) view.findViewById(R.id.no_key_selected);
|
||||||
mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id);
|
mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id);
|
||||||
mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest);
|
mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest);
|
||||||
mKeyMasterKeyIdHex = (TextView) view.findViewById(R.id.select_secret_key_master_key_hex);
|
mKeyMasterKeyIdHex = (TextView) view.findViewById(R.id.select_secret_key_master_key_hex);
|
||||||
|
@ -26,10 +26,7 @@ import android.support.v4.app.Fragment;
|
|||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -135,7 +132,9 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
mSecretKey.setText(R.string.secret_key_yes);
|
mSecretKey.setText(R.string.secret_key_yes);
|
||||||
|
|
||||||
// certify button
|
// certify button
|
||||||
mActionCertify.setVisibility(View.GONE);
|
// TODO this button MIGHT be useful if the user wants to
|
||||||
|
// certify a private key with another...
|
||||||
|
// mActionCertify.setVisibility(View.GONE);
|
||||||
|
|
||||||
// edit button
|
// edit button
|
||||||
mActionEdit.setVisibility(View.VISIBLE);
|
mActionEdit.setVisibility(View.VISIBLE);
|
||||||
@ -156,17 +155,19 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
|
|
||||||
// certify button
|
// certify button
|
||||||
mActionCertify.setVisibility(View.VISIBLE);
|
mActionCertify.setVisibility(View.VISIBLE);
|
||||||
mActionCertify.setOnClickListener(new View.OnClickListener() {
|
|
||||||
public void onClick(View view) {
|
|
||||||
certifyKey(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(
|
|
||||||
Long.toString(masterKeyId)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// edit button
|
// edit button
|
||||||
mActionEdit.setVisibility(View.GONE);
|
mActionEdit.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO see todo note above, doing this here for now
|
||||||
|
mActionCertify.setOnClickListener(new View.OnClickListener() {
|
||||||
|
public void onClick(View view) {
|
||||||
|
certifyKey(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(
|
||||||
|
Long.toString(masterKeyId)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mActionEncrypt.setOnClickListener(new View.OnClickListener() {
|
mActionEncrypt.setOnClickListener(new View.OnClickListener() {
|
||||||
@ -198,13 +199,13 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
static final int KEYRING_INDEX_USER_ID = 2;
|
static final int KEYRING_INDEX_USER_ID = 2;
|
||||||
|
|
||||||
static final String[] USER_IDS_PROJECTION =
|
static final String[] USER_IDS_PROJECTION =
|
||||||
new String[]{KeychainContract.UserIds._ID, KeychainContract.UserIds.USER_ID,
|
new String[]{
|
||||||
KeychainContract.UserIds.RANK, };
|
KeychainContract.UserIds._ID,
|
||||||
// not the main user id
|
KeychainContract.UserIds.USER_ID,
|
||||||
static final String USER_IDS_SELECTION =
|
KeychainContract.UserIds.RANK,
|
||||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " > 0 ";
|
};
|
||||||
static final String USER_IDS_SORT_ORDER =
|
static final String USER_IDS_SORT_ORDER =
|
||||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.USER_ID + " COLLATE LOCALIZED ASC";
|
KeychainContract.UserIds.RANK + " COLLATE LOCALIZED ASC";
|
||||||
|
|
||||||
static final String[] KEYS_PROJECTION =
|
static final String[] KEYS_PROJECTION =
|
||||||
new String[]{KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID,
|
new String[]{KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID,
|
||||||
@ -240,7 +241,7 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
|
|
||||||
// Now create and return a CursorLoader that will take care of
|
// Now create and return a CursorLoader that will take care of
|
||||||
// creating a Cursor for the data being displayed.
|
// creating a Cursor for the data being displayed.
|
||||||
return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null,
|
return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null,
|
||||||
USER_IDS_SORT_ORDER);
|
USER_IDS_SORT_ORDER);
|
||||||
}
|
}
|
||||||
case LOADER_ID_KEYS: {
|
case LOADER_ID_KEYS: {
|
||||||
@ -322,7 +323,7 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
}
|
}
|
||||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
|
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
|
||||||
|
|
||||||
mFingerprint.setText(colorizeFingerprint(fingerprint));
|
mFingerprint.setText(OtherHelper.colorizeFingerprint(fingerprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
mKeysAdapter.swapCursor(data);
|
mKeysAdapter.swapCursor(data);
|
||||||
@ -333,63 +334,6 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpannableStringBuilder colorizeFingerprint(String fingerprint) {
|
|
||||||
SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
|
|
||||||
try {
|
|
||||||
// for each 4 characters of the fingerprint + 1 space
|
|
||||||
for (int i = 0; i < fingerprint.length(); i += 5) {
|
|
||||||
int spanEnd = Math.min(i + 4, fingerprint.length());
|
|
||||||
String fourChars = fingerprint.substring(i, spanEnd);
|
|
||||||
|
|
||||||
int raw = Integer.parseInt(fourChars, 16);
|
|
||||||
byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)};
|
|
||||||
int[] color = OtherHelper.getRgbForData(bytes);
|
|
||||||
int r = color[0];
|
|
||||||
int g = color[1];
|
|
||||||
int b = color[2];
|
|
||||||
|
|
||||||
// we cannot change black by multiplication, so adjust it to an almost-black grey,
|
|
||||||
// which will then be brightened to the minimal brightness level
|
|
||||||
if (r == 0 && g == 0 && b == 0) {
|
|
||||||
r = 1;
|
|
||||||
g = 1;
|
|
||||||
b = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert rgb to brightness
|
|
||||||
double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
||||||
|
|
||||||
// If a color is too dark to be seen on black,
|
|
||||||
// then brighten it up to a minimal brightness.
|
|
||||||
if (brightness < 80) {
|
|
||||||
double factor = 80.0 / brightness;
|
|
||||||
r = Math.min(255, (int) (r * factor));
|
|
||||||
g = Math.min(255, (int) (g * factor));
|
|
||||||
b = Math.min(255, (int) (b * factor));
|
|
||||||
|
|
||||||
// If it is too light, then darken it to a respective maximal brightness.
|
|
||||||
} else if (brightness > 180) {
|
|
||||||
double factor = 180.0 / brightness;
|
|
||||||
r = (int) (r * factor);
|
|
||||||
g = (int) (g * factor);
|
|
||||||
b = (int) (b * factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a foreground color with the 3 digest integers as RGB
|
|
||||||
// and then converting that int to hex to use as a color
|
|
||||||
sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)),
|
|
||||||
i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(Constants.TAG, "Colorization failed", e);
|
|
||||||
// if anything goes wrong, then just display the fingerprint without colour,
|
|
||||||
// instead of partially correct colour or wrong colours
|
|
||||||
return new SpannableStringBuilder(fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
||||||
* We need to make sure we are no longer using it.
|
* We need to make sure we are no longer using it.
|
||||||
|
@ -23,27 +23,49 @@ import android.support.v4.widget.CursorAdapter;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
private int mIndexUserId;
|
private int mIndexUserId, mIndexRank;
|
||||||
private int mVerifiedId;
|
private int mVerifiedId;
|
||||||
|
|
||||||
public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) {
|
final private ArrayList<Boolean> mCheckStates;
|
||||||
|
|
||||||
|
public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
|
|
||||||
mInflater = LayoutInflater.from(context);
|
mInflater = LayoutInflater.from(context);
|
||||||
|
|
||||||
|
mCheckStates = showCheckBoxes ? new ArrayList<Boolean>() : null;
|
||||||
|
|
||||||
initIndex(c);
|
initIndex(c);
|
||||||
}
|
}
|
||||||
|
public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) {
|
||||||
|
this(context, c, flags, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
initIndex(newCursor);
|
initIndex(newCursor);
|
||||||
|
if(mCheckStates != null) {
|
||||||
|
mCheckStates.clear();
|
||||||
|
if(newCursor != null) {
|
||||||
|
int count = newCursor.getCount();
|
||||||
|
mCheckStates.ensureCapacity(count);
|
||||||
|
// initialize to true (use case knowledge: we usually want to sign all uids)
|
||||||
|
for(int i = 0; i < count; i++)
|
||||||
|
mCheckStates.add(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return super.swapCursor(newCursor);
|
return super.swapCursor(newCursor);
|
||||||
}
|
}
|
||||||
@ -57,22 +79,69 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
|||||||
private void initIndex(Cursor cursor) {
|
private void initIndex(Cursor cursor) {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||||
|
mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK);
|
||||||
mVerifiedId = cursor.getColumnIndexOrThrow("verified");
|
mVerifiedId = cursor.getColumnIndexOrThrow("verified");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
String userIdStr = cursor.getString(mIndexUserId);
|
|
||||||
int verified = cursor.getInt(mVerifiedId);
|
|
||||||
|
|
||||||
TextView userId = (TextView) view.findViewById(R.id.userId);
|
TextView vRank = (TextView) view.findViewById(R.id.rank);
|
||||||
userId.setText(userIdStr + (verified > 0 ? " (ok)" : "(nope)"));
|
TextView vUserId = (TextView) view.findViewById(R.id.userId);
|
||||||
|
TextView vAddress = (TextView) view.findViewById(R.id.address);
|
||||||
|
|
||||||
|
vRank.setText(Integer.toString(cursor.getInt(mIndexRank)));
|
||||||
|
|
||||||
|
String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexUserId));
|
||||||
|
int verified = cursor.getInt(mVerifiedId);
|
||||||
|
if (userId[0] != null) {
|
||||||
|
vUserId.setText(userId[0] + (verified > 0 ? " (ok)" : "(nope)"));
|
||||||
|
} else {
|
||||||
|
vUserId.setText(R.string.user_id_no_name);
|
||||||
|
}
|
||||||
|
vAddress.setText(userId[1]);
|
||||||
|
|
||||||
|
// don't care further if checkboxes aren't shown
|
||||||
|
if(mCheckStates == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox);
|
||||||
|
final int position = cursor.getPosition();
|
||||||
|
vCheckBox.setClickable(false);
|
||||||
|
vCheckBox.setChecked(mCheckStates.get(position));
|
||||||
|
vCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||||
|
mCheckStates.set(position, b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
view.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
vCheckBox.toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<String> getSelectedUserIds() {
|
||||||
|
ArrayList<String> result = new ArrayList<String>();
|
||||||
|
for(int i = 0; i < mCheckStates.size(); i++) {
|
||||||
|
if(mCheckStates.get(i)) {
|
||||||
|
mCursor.moveToPosition(i);
|
||||||
|
result.add(mCursor.getString(mIndexUserId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
return mInflater.inflate(R.layout.view_key_userids_item, null);
|
View view = mInflater.inflate(R.layout.view_key_userids_item, null);
|
||||||
|
// only need to do this once ever, since mShowCheckBoxes is final
|
||||||
|
view.findViewById(R.id.checkBox).setVisibility(mCheckStates != null ? View.VISIBLE : View.GONE);
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,93 @@
|
|||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
tools:layout="@layout/select_secret_key_layout_fragment" />
|
tools:layout="@layout/select_secret_key_layout_fragment" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/SectionHeader"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:text="KEY TO SIGN" />
|
||||||
|
|
||||||
|
<TableLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:shrinkColumns="1">
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"
|
||||||
|
android:text="@string/label_key_id" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_id"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingRight="5dip"
|
||||||
|
android:text=""
|
||||||
|
android:typeface="monospace" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"
|
||||||
|
android:text="@string/label_main_user_id" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_user_id"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:typeface="monospace" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:paddingRight="10dip"
|
||||||
|
android:text="@string/label_fingerprint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fingerprint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:typeface="monospace" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
</TableLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/SectionHeader"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:text="@string/section_uids_to_sign" />
|
||||||
|
|
||||||
|
<org.sufficientlysecure.keychain.ui.widget.FixedListView
|
||||||
|
android:id="@+id/user_ids"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/SectionHeader"
|
style="@style/SectionHeader"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -52,16 +52,25 @@
|
|||||||
android:layout_marginRight="5dip"
|
android:layout_marginRight="5dip"
|
||||||
android:text=""
|
android:text=""
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:paddingLeft="10dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/select_secret_key_master_key_hex"
|
android:id="@+id/select_secret_key_master_key_hex"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="left"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_marginRight="15dip" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/no_key_selected"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:text="@string/api_settings_no_key"
|
android:text="@string/api_settings_no_key"
|
||||||
android:layout_marginRight="5dip" />
|
android:layout_marginTop="15dp" />
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -2,15 +2,44 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="horizontal"
|
||||||
android:paddingRight="3dip"
|
android:paddingRight="3dip"
|
||||||
android:singleLine="true">
|
android:singleLine="true">
|
||||||
|
|
||||||
<TextView
|
<CheckBox
|
||||||
android:id="@+id/userId"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/user_id"
|
android:id="@+id/checkBox" />
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
<TextView
|
||||||
|
android:id="@+id/rank"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:text="0"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:layout_gravity="center_vertical" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/userId"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/user_id_no_name"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/address"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/label_email"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:paddingLeft="10dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
@ -465,6 +465,8 @@
|
|||||||
<string name="label_secret_key">Secret Key</string>
|
<string name="label_secret_key">Secret Key</string>
|
||||||
<string name="secret_key_yes">available</string>
|
<string name="secret_key_yes">available</string>
|
||||||
<string name="secret_key_no">unavailable</string>
|
<string name="secret_key_no">unavailable</string>
|
||||||
|
|
||||||
|
<!-- unsorted -->
|
||||||
<string name="show_unknown_signatures">Show unknown signatures</string>
|
<string name="show_unknown_signatures">Show unknown signatures</string>
|
||||||
<string name="section_signer_id">Signer</string>
|
<string name="section_signer_id">Signer</string>
|
||||||
<string name="section_cert">Certificate Details</string>
|
<string name="section_cert">Certificate Details</string>
|
||||||
@ -476,5 +478,7 @@
|
|||||||
<string name="label_subkey_rank">Subkey Rank</string>
|
<string name="label_subkey_rank">Subkey Rank</string>
|
||||||
<string name="unknown_uid"><![CDATA[<unknown>]]></string>
|
<string name="unknown_uid"><![CDATA[<unknown>]]></string>
|
||||||
<string name="empty_certs">No certificates for this key</string>
|
<string name="empty_certs">No certificates for this key</string>
|
||||||
|
<string name="section_uids_to_sign">User IDs to sign</string>
|
||||||
|
<string name="progress_re_adding_certs">Reapplying certificates</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user