Merge branch 'development' of github.com:open-keychain/open-keychain into development

This commit is contained in:
Dominik Schürmann 2015-01-14 00:04:10 +01:00
commit 318d3eb120
18 changed files with 698 additions and 102 deletions

View File

@ -343,6 +343,18 @@ public abstract class OperationResult implements Parcelable {
MSG_IP_UID_REORDER(LogLevel.DEBUG, R.string.msg_ip_uid_reorder), MSG_IP_UID_REORDER(LogLevel.DEBUG, R.string.msg_ip_uid_reorder),
MSG_IP_UID_PROCESSING (LogLevel.DEBUG, R.string.msg_ip_uid_processing), MSG_IP_UID_PROCESSING (LogLevel.DEBUG, R.string.msg_ip_uid_processing),
MSG_IP_UID_REVOKED (LogLevel.DEBUG, R.string.msg_ip_uid_revoked), MSG_IP_UID_REVOKED (LogLevel.DEBUG, R.string.msg_ip_uid_revoked),
MSG_IP_UAT_CLASSIFYING (LogLevel.DEBUG, R.string.msg_ip_uat_classifying),
MSG_IP_UAT_PROCESSING_IMAGE (LogLevel.DEBUG, R.string.msg_ip_uat_processing_image),
MSG_IP_UAT_PROCESSING_UNKNOWN (LogLevel.DEBUG, R.string.msg_ip_uat_processing_unknown),
MSG_IP_UAT_REVOKED (LogLevel.DEBUG, R.string.msg_ip_uat_revoked),
MSG_IP_UAT_CERT_BAD (LogLevel.WARN, R.string.msg_ip_uat_cert_bad),
MSG_IP_UAT_CERT_OLD (LogLevel.DEBUG, R.string.msg_ip_uat_cert_old),
MSG_IP_UAT_CERT_NONREVOKE (LogLevel.DEBUG, R.string.msg_ip_uat_cert_nonrevoke),
MSG_IP_UAT_CERT_NEW (LogLevel.DEBUG, R.string.msg_ip_uat_cert_new),
MSG_IP_UAT_CERT_ERROR (LogLevel.WARN, R.string.msg_ip_uat_cert_error),
MSG_IP_UAT_CERTS_UNKNOWN (LogLevel.DEBUG, R.plurals.msg_ip_uat_certs_unknown),
MSG_IP_UAT_CERT_GOOD_REVOKE (LogLevel.DEBUG, R.string.msg_ip_uat_cert_good_revoke),
MSG_IP_UAT_CERT_GOOD (LogLevel.DEBUG, R.string.msg_ip_uat_cert_good),
// import secret // import secret
MSG_IS(LogLevel.START, R.string.msg_is), MSG_IS(LogLevel.START, R.string.msg_is),
@ -416,6 +428,21 @@ public abstract class OperationResult implements Parcelable {
MSG_KC_UID_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uid_revoke_old), MSG_KC_UID_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uid_revoke_old),
MSG_KC_UID_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uid_remove), MSG_KC_UID_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uid_remove),
MSG_KC_UID_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uid_warn_encoding), MSG_KC_UID_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uid_warn_encoding),
MSG_KC_UAT_JPEG (LogLevel.DEBUG, R.string.msg_kc_uat_jpeg),
MSG_KC_UAT_UNKNOWN (LogLevel.DEBUG, R.string.msg_kc_uat_unknown),
MSG_KC_UAT_BAD_ERR (LogLevel.WARN, R.string.msg_kc_uat_bad_err),
MSG_KC_UAT_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_uat_bad_local),
MSG_KC_UAT_BAD_TIME (LogLevel.WARN, R.string.msg_kc_uat_bad_time),
MSG_KC_UAT_BAD_TYPE (LogLevel.WARN, R.string.msg_kc_uat_bad_type),
MSG_KC_UAT_BAD (LogLevel.WARN, R.string.msg_kc_uat_bad),
MSG_KC_UAT_CERT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_cert_dup),
MSG_KC_UAT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_dup),
MSG_KC_UAT_FOREIGN (LogLevel.DEBUG, R.string.msg_kc_uat_foreign),
MSG_KC_UAT_NO_CERT (LogLevel.DEBUG, R.string.msg_kc_uat_no_cert),
MSG_KC_UAT_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_dup),
MSG_KC_UAT_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_old),
MSG_KC_UAT_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uat_remove),
MSG_KC_UAT_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uat_warn_encoding),
// keyring consolidation // keyring consolidation
@ -480,6 +507,8 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary), MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary),
MSG_MF_UID_REVOKE (LogLevel.INFO, R.string.msg_mf_uid_revoke), MSG_MF_UID_REVOKE (LogLevel.INFO, R.string.msg_mf_uid_revoke),
MSG_MF_UID_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_mf_uid_error_empty), MSG_MF_UID_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_mf_uid_error_empty),
MSG_MF_UAT_ADD_IMAGE (LogLevel.INFO, R.string.msg_mf_uat_add_image),
MSG_MF_UAT_ADD_UNKNOWN (LogLevel.INFO, R.string.msg_mf_uat_add_unknown),
MSG_MF_UNLOCK_ERROR (LogLevel.ERROR, R.string.msg_mf_unlock_error), MSG_MF_UNLOCK_ERROR (LogLevel.ERROR, R.string.msg_mf_unlock_error),
MSG_MF_UNLOCK (LogLevel.DEBUG, R.string.msg_mf_unlock), MSG_MF_UNLOCK (LogLevel.DEBUG, R.string.msg_mf_unlock),

View File

@ -34,6 +34,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
@ -478,7 +479,7 @@ public class PgpKeyOperation {
PGPPublicKey modifiedPublicKey = masterPublicKey; PGPPublicKey modifiedPublicKey = masterPublicKey;
// 2a. Add certificates for new user ids // 2a. Add certificates for new user ids
subProgressPush(15, 25); subProgressPush(15, 23);
for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) { for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) {
progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size())); progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size()));
@ -522,8 +523,33 @@ public class PgpKeyOperation {
} }
subProgressPop(); subProgressPop();
// 2b. Add revocations for revoked user ids // 2b. Add certificates for new user ids
subProgressPush(25, 40); subProgressPush(23, 32);
for (int i = 0; i < saveParcel.mAddUserAttribute.size(); i++) {
progress(R.string.progress_modify_adduat, (i - 1) * (100 / saveParcel.mAddUserAttribute.size()));
WrappedUserAttribute attribute = saveParcel.mAddUserAttribute.get(i);
switch (attribute.getType()) {
case WrappedUserAttribute.UAT_NONE:
log.add(LogType.MSG_MF_UAT_ADD_UNKNOWN, indent);
break;
case WrappedUserAttribute.UAT_IMAGE:
log.add(LogType.MSG_MF_UAT_ADD_IMAGE, indent);
break;
}
PGPUserAttributeSubpacketVector vector = attribute.getVector();
// generate and add new certificate
PGPSignature cert = generateUserAttributeSignature(masterPrivateKey,
masterPublicKey, vector);
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
}
subProgressPop();
// 2c. Add revocations for revoked user ids
subProgressPush(32, 40);
for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) { for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) {
progress(R.string.progress_modify_revokeuid, (i - 1) * (100 / saveParcel.mRevokeUserIds.size())); progress(R.string.progress_modify_revokeuid, (i - 1) * (100 / saveParcel.mRevokeUserIds.size()));
@ -1174,6 +1200,26 @@ public class PgpKeyOperation {
return sGen.generateCertification(userId, pKey); return sGen.generateCertification(userId, pKey);
} }
private static PGPSignature generateUserAttributeSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
PGPUserAttributeSubpacketVector vector)
throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{
/* critical subpackets: we consider those important for a modern pgp implementation */
hashedPacketsGen.setSignatureCreationTime(true, new Date());
}
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(vector, pKey);
}
private static PGPSignature generateRevocationSignature( private static PGPSignature generateRevocationSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
throws IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {

View File

@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags; import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.UserAttributeSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPObjectFactory;
@ -30,6 +31,7 @@ import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
@ -605,6 +607,170 @@ public class UncachedKeyRing {
return null; return null;
} }
ArrayList<PGPUserAttributeSubpacketVector> processedUserAttributes = new ArrayList<>();
for (PGPUserAttributeSubpacketVector userAttribute :
new IterableIterator<PGPUserAttributeSubpacketVector>(masterKey.getUserAttributes())) {
if (userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE) != null) {
log.add(LogType.MSG_KC_UAT_JPEG, indent);
} else {
log.add(LogType.MSG_KC_UAT_UNKNOWN, indent);
}
try {
indent += 1;
// check for duplicate user attributes
if (processedUserAttributes.contains(userAttribute)) {
log.add(LogType.MSG_KC_UAT_DUP, indent);
// strip out the first found user id with this name
modified = PGPPublicKey.removeCertification(modified, userAttribute);
}
processedUserAttributes.add(userAttribute);
PGPSignature selfCert = null;
revocation = null;
// look through signatures for this specific user id
@SuppressWarnings("unchecked")
Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForUserAttribute(userAttribute);
if (signaturesIt != null) {
for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) {
WrappedSignature cert = new WrappedSignature(zert);
long certId = cert.getKeyId();
int type = zert.getSignatureType();
if (type != PGPSignature.DEFAULT_CERTIFICATION
&& type != PGPSignature.NO_CERTIFICATION
&& type != PGPSignature.CASUAL_CERTIFICATION
&& type != PGPSignature.POSITIVE_CERTIFICATION
&& type != PGPSignature.CERTIFICATION_REVOCATION) {
log.add(LogType.MSG_KC_UAT_BAD_TYPE,
indent, "0x" + Integer.toString(zert.getSignatureType(), 16));
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
if (cert.getCreationTime().after(nowPlusOneDay)) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_UAT_BAD_TIME, indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_UAT_BAD_LOCAL, indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
// If this is a foreign signature, ...
if (certId != masterKeyId) {
// never mind any further for public keys, but remove them from secret ones
if (isSecret()) {
log.add(LogType.MSG_KC_UAT_FOREIGN,
indent, KeyFormattingUtils.convertKeyIdToHex(certId));
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
}
continue;
}
// Otherwise, first make sure it checks out
try {
cert.init(masterKey);
if (!cert.verifySignature(masterKey, userAttribute)) {
log.add(LogType.MSG_KC_UAT_BAD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
} catch (PgpGeneralException e) {
log.add(LogType.MSG_KC_UAT_BAD_ERR,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
badCerts += 1;
continue;
}
switch (type) {
case PGPSignature.DEFAULT_CERTIFICATION:
case PGPSignature.NO_CERTIFICATION:
case PGPSignature.CASUAL_CERTIFICATION:
case PGPSignature.POSITIVE_CERTIFICATION:
if (selfCert == null) {
selfCert = zert;
} else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_CERT_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, selfCert);
redundantCerts += 1;
selfCert = zert;
} else {
log.add(LogType.MSG_KC_UAT_CERT_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
}
// If there is a revocation certificate, and it's older than this, drop it
if (revocation != null
&& revocation.getCreationTime().before(selfCert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
revocation = null;
redundantCerts += 1;
}
break;
case PGPSignature.CERTIFICATION_REVOCATION:
// If this is older than the (latest) self cert, drop it
if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
continue;
}
// first revocation? remember it.
if (revocation == null) {
revocation = zert;
// more revocations? at least one is superfluous, then.
} else if (revocation.getCreationTime().before(cert.getCreationTime())) {
log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
redundantCerts += 1;
revocation = zert;
} else {
log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
redundantCerts += 1;
}
break;
}
}
}
// If no valid certificate (if only a revocation) remains, drop it
if (selfCert == null && revocation == null) {
log.add(LogType.MSG_KC_UAT_REMOVE,
indent);
modified = PGPPublicKey.removeCertification(modified, userAttribute);
}
} finally {
indent -= 1;
}
}
// Replace modified key in the keyring // Replace modified key in the keyring
ring = replacePublicKey(ring, modified); ring = replacePublicKey(ring, modified);
indent -= 1; indent -= 1;
@ -852,8 +1018,8 @@ public class UncachedKeyRing {
/** This operation merges information from a different keyring, returning a combined /** This operation merges information from a different keyring, returning a combined
* UncachedKeyRing. * UncachedKeyRing.
* *
* The combined keyring contains the subkeys and user ids of both input keyrings, but it does * The combined keyring contains the subkeys, user ids and user attributes of both input
* not necessarily have the canonicalized property. * keyrings, but it does not necessarily have the canonicalized property.
* *
* @param other The UncachedKeyRing to merge. Must not be empty, and of the same masterKeyId * @param other The UncachedKeyRing to merge. Must not be empty, and of the same masterKeyId
* @return A consolidated UncachedKeyRing with the data of both input keyrings. Same type as * @return A consolidated UncachedKeyRing with the data of both input keyrings. Same type as
@ -973,6 +1139,32 @@ public class UncachedKeyRing {
modified = PGPPublicKey.addCertification(modified, rawUserId, cert); modified = PGPPublicKey.addCertification(modified, rawUserId, cert);
} }
} }
// Copy over all user attribute certificates
for (PGPUserAttributeSubpacketVector vector :
new IterableIterator<PGPUserAttributeSubpacketVector>(key.getUserAttributes())) {
@SuppressWarnings("unchecked")
Iterator<PGPSignature> signaturesIt = key.getSignaturesForUserAttribute(vector);
// no signatures for this user attribute attribute, skip it
if (signaturesIt == null) {
continue;
}
for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) {
// Don't merge foreign stuff into secret keys
if (cert.getKeyID() != masterKeyId && isSecret()) {
continue;
}
byte[] encoded = cert.getEncoded();
// Known cert, skip it
if (certs.contains(encoded)) {
continue;
}
newCerts += 1;
certs.add(encoded);
modified = PGPPublicKey.addCertification(modified, vector, cert);
}
}
// If anything changed, save the updated (sub)key // If anything changed, save the updated (sub)key
if (modified != resultKey) { if (modified != resultKey) {
result = replacePublicKey(result, modified); result = replacePublicKey(result, modified);

View File

@ -24,6 +24,7 @@ import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
@ -215,6 +216,15 @@ public class UncachedPublicKey {
return userIds; return userIds;
} }
public ArrayList<WrappedUserAttribute> getUnorderedUserAttributes() {
ArrayList<WrappedUserAttribute> userAttributes = new ArrayList<WrappedUserAttribute>();
for (PGPUserAttributeSubpacketVector userAttribute :
new IterableIterator<PGPUserAttributeSubpacketVector>(mPublicKey.getUserAttributes())) {
userAttributes.add(new WrappedUserAttribute(userAttribute));
}
return userAttributes;
}
public boolean isElGamalEncrypt() { public boolean isElGamalEncrypt() {
return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT; return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT;
} }
@ -270,6 +280,25 @@ public class UncachedPublicKey {
} }
} }
public Iterator<WrappedSignature> getSignaturesForUserAttribute(WrappedUserAttribute attribute) {
final Iterator<PGPSignature> it = mPublicKey.getSignaturesForUserAttribute(attribute.getVector());
if (it != null) {
return new Iterator<WrappedSignature>() {
public void remove() {
it.remove();
}
public WrappedSignature next() {
return new WrappedSignature(it.next());
}
public boolean hasNext() {
return it.hasNext();
}
};
} else {
return null;
}
}
/** Get all key usage flags. /** Get all key usage flags.
* If at least one key flag subpacket is present return these. If no * If at least one key flag subpacket is present return these. If no
* subpacket is present it returns null. * subpacket is present it returns null.
@ -299,4 +328,5 @@ public class UncachedPublicKey {
} }
return mCacheUsage; return mCacheUsage;
} }
} }

View File

@ -29,6 +29,7 @@ import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList; import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@ -199,12 +200,23 @@ public class WrappedSignature {
} }
} }
boolean verifySignature(PGPPublicKey key, PGPUserAttributeSubpacketVector attribute) throws PgpGeneralException {
try {
return mSig.verifyCertification(attribute, key);
} catch (PGPException e) {
throw new PgpGeneralException("Error!", e);
}
}
public boolean verifySignature(UncachedPublicKey key, byte[] rawUserId) throws PgpGeneralException { public boolean verifySignature(UncachedPublicKey key, byte[] rawUserId) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), rawUserId); return verifySignature(key.getPublicKey(), rawUserId);
} }
public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException { public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), uid); return verifySignature(key.getPublicKey(), uid);
} }
public boolean verifySignature(UncachedPublicKey key, WrappedUserAttribute attribute) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), attribute.getVector());
}
public static WrappedSignature fromBytes(byte[] data) { public static WrappedSignature fromBytes(byte[] data) {
PGPObjectFactory factory = new PGPObjectFactory(data); PGPObjectFactory factory = new PGPObjectFactory(data);

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.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.pgp;
import org.spongycastle.bcpg.BCPGInputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.bcpg.Packet;
import org.spongycastle.bcpg.UserAttributePacket;
import org.spongycastle.bcpg.UserAttributeSubpacket;
import org.spongycastle.bcpg.UserAttributeSubpacketTags;
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class WrappedUserAttribute implements Serializable {
public static final int UAT_NONE = 0;
public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE;
private PGPUserAttributeSubpacketVector mVector;
WrappedUserAttribute(PGPUserAttributeSubpacketVector vector) {
mVector = vector;
}
PGPUserAttributeSubpacketVector getVector() {
return mVector;
}
public int getType() {
UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray();
if (subpackets.length > 0) {
return subpackets[0].getType();
}
return 0;
}
public static WrappedUserAttribute fromSubpacket (int type, byte[] data) {
UserAttributeSubpacket subpacket = new UserAttributeSubpacket(type, data);
PGPUserAttributeSubpacketVector vector = new PGPUserAttributeSubpacketVector(
new UserAttributeSubpacket[] { subpacket });
return new WrappedUserAttribute(vector);
}
public byte[] getEncoded () throws IOException {
UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray();
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (UserAttributeSubpacket subpacket : subpackets) {
subpacket.encode(out);
}
return out.toByteArray();
}
public static WrappedUserAttribute fromData (byte[] data) {
// TODO
return null;
}
/** Writes this object to an ObjectOutputStream. */
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BCPGOutputStream bcpg = new BCPGOutputStream(baos);
bcpg.writePacket(new UserAttributePacket(mVector.toSubpacketArray()));
out.writeObject(baos.toByteArray());
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
byte[] data = (byte[]) in.readObject();
BCPGInputStream bcpg = new BCPGInputStream(new ByteArrayInputStream(data));
Packet p = bcpg.readPacket();
if ( ! UserAttributePacket.class.isInstance(p)) {
throw new IOException("Could not decode UserAttributePacket!");
}
mVector = new PGPUserAttributeSubpacketVector(((UserAttributePacket) p).getSubpackets());
}
private void readObjectNoData() throws ObjectStreamException {
}
}

View File

@ -51,9 +51,11 @@ public class KeychainContract {
String EXPIRY = "expiry"; String EXPIRY = "expiry";
} }
interface UserIdsColumns { interface UserPacketsColumns {
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
String TYPE = "type"; // not a database id
String USER_ID = "user_id"; // not a database id String USER_ID = "user_id"; // not a database id
String ATTRIBUTE_DATA = "attribute_data"; // not a database id
String RANK = "rank"; // ONLY used for sorting! no key, no nothing! String RANK = "rank"; // ONLY used for sorting! no key, no nothing!
String IS_PRIMARY = "is_primary"; String IS_PRIMARY = "is_primary";
String IS_REVOKED = "is_revoked"; String IS_REVOKED = "is_revoked";
@ -105,7 +107,7 @@ public class KeychainContract {
public static final String BASE_API_APPS = "api_apps"; public static final String BASE_API_APPS = "api_apps";
public static final String PATH_ACCOUNTS = "accounts"; public static final String PATH_ACCOUNTS = "accounts";
public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns { public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns {
public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID; public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID;
public static final String IS_REVOKED = KeysColumns.IS_REVOKED; public static final String IS_REVOKED = KeysColumns.IS_REVOKED;
public static final String VERIFIED = CertsColumns.VERIFIED; public static final String VERIFIED = CertsColumns.VERIFIED;
@ -225,7 +227,7 @@ public class KeychainContract {
} }
public static class UserIds implements UserIdsColumns, BaseColumns { public static class UserPackets implements UserPacketsColumns, BaseColumns {
public static final String VERIFIED = "verified"; public static final String VERIFIED = "verified";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build(); .appendPath(BASE_KEY_RINGS).build();
@ -304,7 +306,7 @@ public class KeychainContract {
} }
public static class Certs implements CertsColumns, BaseColumns { public static class Certs implements CertsColumns, BaseColumns {
public static final String USER_ID = UserIdsColumns.USER_ID; public static final String USER_ID = UserPacketsColumns.USER_ID;
public static final String SIGNER_UID = "signer_user_id"; public static final String SIGNER_UID = "signer_user_id";
public static final int UNVERIFIED = 0; public static final int UNVERIFIED = 0;

View File

@ -33,7 +33,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity; import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -52,7 +52,7 @@ import java.io.IOException;
*/ */
public class KeychainDatabase extends SQLiteOpenHelper { public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db"; private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 6; private static final int DATABASE_VERSION = 7;
static Boolean apgHack = false; static Boolean apgHack = false;
private Context mContext; private Context mContext;
@ -60,7 +60,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_RINGS_PUBLIC = "keyrings_public"; String KEY_RINGS_PUBLIC = "keyrings_public";
String KEY_RINGS_SECRET = "keyrings_secret"; String KEY_RINGS_SECRET = "keyrings_secret";
String KEYS = "keys"; String KEYS = "keys";
String USER_IDS = "user_ids"; String USER_PACKETS = "user_ids";
String CERTS = "certs"; String CERTS = "certs";
String API_APPS = "api_apps"; String API_APPS = "api_apps";
String API_ACCOUNTS = "api_accounts"; String API_ACCOUNTS = "api_accounts";
@ -106,18 +106,20 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")"; + ")";
private static final String CREATE_USER_IDS = private static final String CREATE_USER_PACKETS =
"CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS + "(" "CREATE TABLE IF NOT EXISTS " + Tables.USER_PACKETS + "("
+ UserIdsColumns.MASTER_KEY_ID + " INTEGER, " + UserPacketsColumns.MASTER_KEY_ID + " INTEGER, "
+ UserIdsColumns.USER_ID + " TEXT, " + UserPacketsColumns.TYPE + " INT, "
+ UserPacketsColumns.USER_ID + " TEXT, "
+ UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, "
+ UserIdsColumns.IS_PRIMARY + " INTEGER, " + UserPacketsColumns.IS_PRIMARY + " INTEGER, "
+ UserIdsColumns.IS_REVOKED + " INTEGER, " + UserPacketsColumns.IS_REVOKED + " INTEGER, "
+ UserIdsColumns.RANK+ " INTEGER, " + UserPacketsColumns.RANK+ " INTEGER, "
+ "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), " + "PRIMARY KEY(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.USER_ID + "), "
+ "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), " + "UNIQUE (" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + "), "
+ "FOREIGN KEY(" + UserIdsColumns.MASTER_KEY_ID + ") REFERENCES " + "FOREIGN KEY(" + UserPacketsColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")"; + ")";
@ -138,7 +140,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES " + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE," + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE,"
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES " + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES "
+ Tables.USER_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") ON DELETE CASCADE" + Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
+ ")"; + ")";
private static final String CREATE_API_APPS = private static final String CREATE_API_APPS =
@ -189,7 +191,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEYRINGS_PUBLIC); db.execSQL(CREATE_KEYRINGS_PUBLIC);
db.execSQL(CREATE_KEYRINGS_SECRET); db.execSQL(CREATE_KEYRINGS_SECRET);
db.execSQL(CREATE_KEYS); db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS); db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS); db.execSQL(CREATE_CERTS);
db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS); db.execSQL(CREATE_API_APPS_ACCOUNTS);
@ -238,6 +240,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
case 5: case 5:
// do consolidate for 3.0 beta3 // do consolidate for 3.0 beta3
// fall through // fall through
case 6:
db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
} }
// always do consolidate after upgrade // always do consolidate after upgrade

View File

@ -37,7 +37,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -205,7 +206,7 @@ public class KeychainProvider extends ContentProvider {
return Keys.CONTENT_TYPE; return Keys.CONTENT_TYPE;
case KEY_RING_USER_IDS: case KEY_RING_USER_IDS:
return UserIds.CONTENT_TYPE; return UserPackets.CONTENT_TYPE;
case KEY_RING_SECRET: case KEY_RING_SECRET:
return KeyRings.CONTENT_ITEM_TYPE; return KeyRings.CONTENT_ITEM_TYPE;
@ -262,7 +263,7 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY); projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY);
projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM); projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM);
projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT);
projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID); projectionMap.put(KeyRings.USER_ID, UserPackets.USER_ID);
projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED); projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED);
projectionMap.put(KeyRings.PUBKEY_DATA, projectionMap.put(KeyRings.PUBKEY_DATA,
Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA
@ -296,11 +297,12 @@ public class KeychainProvider extends ContentProvider {
qb.setTables( qb.setTables(
Tables.KEYS Tables.KEYS
+ " INNER JOIN " + Tables.USER_IDS + " ON (" + " INNER JOIN " + Tables.USER_PACKETS + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = " + " = "
+ Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = 0" // we KNOW that the rank zero user packet is a user id!
+ " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = 0"
+ ") LEFT JOIN " + Tables.CERTS + " ON (" + ") LEFT JOIN " + Tables.CERTS + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = " + " = "
@ -376,7 +378,7 @@ public class KeychainProvider extends ContentProvider {
String subkey = Long.valueOf(uri.getLastPathSegment()).toString(); String subkey = Long.valueOf(uri.getLastPathSegment()).toString();
qb.appendWhere(" AND EXISTS (" qb.appendWhere(" AND EXISTS ("
+ " SELECT 1 FROM " + Tables.KEYS + " AS tmp" + " SELECT 1 FROM " + Tables.KEYS + " AS tmp"
+ " WHERE tmp." + UserIds.MASTER_KEY_ID + " WHERE tmp." + UserPackets.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND tmp." + Keys.KEY_ID + " = " + subkey + "" + " AND tmp." + Keys.KEY_ID + " = " + subkey + ""
+ ")"); + ")");
@ -398,15 +400,15 @@ public class KeychainProvider extends ContentProvider {
if (i != 0) { if (i != 0) {
emailWhere += " OR "; emailWhere += " OR ";
} }
emailWhere += "tmp." + UserIds.USER_ID + " LIKE "; emailWhere += "tmp." + UserPackets.USER_ID + " LIKE ";
// match '*<email>', so it has to be at the *end* of the user id // match '*<email>', so it has to be at the *end* of the user id
emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
gotCondition = true; gotCondition = true;
} }
if(gotCondition) { if(gotCondition) {
qb.appendWhere(" AND EXISTS (" qb.appendWhere(" AND EXISTS ("
+ " SELECT 1 FROM " + Tables.USER_IDS + " AS tmp" + " SELECT 1 FROM " + Tables.USER_PACKETS + " AS tmp"
+ " WHERE tmp." + UserIds.MASTER_KEY_ID + " WHERE tmp." + UserPackets.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND (" + emailWhere + ")" + " AND (" + emailWhere + ")"
+ ")"); + ")");
@ -420,7 +422,7 @@ public class KeychainProvider extends ContentProvider {
} }
if (TextUtils.isEmpty(sortOrder)) { if (TextUtils.isEmpty(sortOrder)) {
sortOrder = Tables.USER_IDS + "." + UserIds.USER_ID + " ASC"; sortOrder = Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC";
} }
// uri to watch is all /key_rings/ // uri to watch is all /key_rings/
@ -459,36 +461,44 @@ public class KeychainProvider extends ContentProvider {
case KEY_RINGS_USER_IDS: case KEY_RINGS_USER_IDS:
case KEY_RING_USER_IDS: { case KEY_RING_USER_IDS: {
HashMap<String, String> projectionMap = new HashMap<String, String>(); HashMap<String, String> projectionMap = new HashMap<String, String>();
projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id"); projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id");
projectionMap.put(UserIds.MASTER_KEY_ID, Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID); projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID);
projectionMap.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); projectionMap.put(UserPackets.TYPE, Tables.USER_PACKETS + "." + UserPackets.TYPE);
projectionMap.put(UserIds.RANK, Tables.USER_IDS + "." + UserIds.RANK); projectionMap.put(UserPackets.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
projectionMap.put(UserIds.IS_PRIMARY, Tables.USER_IDS + "." + UserIds.IS_PRIMARY); projectionMap.put(UserPackets.ATTRIBUTE_DATA, Tables.USER_PACKETS + "." + UserPackets.ATTRIBUTE_DATA);
projectionMap.put(UserIds.IS_REVOKED, Tables.USER_IDS + "." + UserIds.IS_REVOKED); projectionMap.put(UserPackets.RANK, Tables.USER_PACKETS + "." + UserPackets.RANK);
projectionMap.put(UserPackets.IS_PRIMARY, Tables.USER_PACKETS + "." + UserPackets.IS_PRIMARY);
projectionMap.put(UserPackets.IS_REVOKED, Tables.USER_PACKETS + "." + UserPackets.IS_REVOKED);
// we take the minimum (>0) here, where "1" is "verified by known secret key" // we take the minimum (>0) here, where "1" is "verified by known secret key"
projectionMap.put(UserIds.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserIds.VERIFIED); projectionMap.put(UserPackets.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserPackets.VERIFIED);
qb.setProjectionMap(projectionMap); qb.setProjectionMap(projectionMap);
qb.setTables(Tables.USER_IDS qb.setTables(Tables.USER_PACKETS
+ " LEFT JOIN " + Tables.CERTS + " ON (" + " LEFT JOIN " + Tables.CERTS + " ON ("
+ Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = "
+ Tables.CERTS + "." + Certs.MASTER_KEY_ID + Tables.CERTS + "." + Certs.MASTER_KEY_ID
+ " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = " + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = "
+ Tables.CERTS + "." + Certs.RANK + Tables.CERTS + "." + Certs.RANK
+ " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0"
+ ")"); + ")");
groupBy = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ ", " + Tables.USER_IDS + "." + UserIds.RANK; + ", " + Tables.USER_PACKETS + "." + UserPackets.RANK;
// for now, we only respect user ids here, so TYPE must be NULL
// TODO expand with KEY_RING_USER_PACKETS query type which lifts this restriction
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL");
// If we are searching for a particular keyring's ids, add where // If we are searching for a particular keyring's ids, add where
if (match == KEY_RING_USER_IDS) { if (match == KEY_RING_USER_IDS) {
qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "); // TODO remove with the thing above
qb.appendWhere(" AND ");
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1)); qb.appendWhereEscapeString(uri.getPathSegments().get(1));
} }
if (TextUtils.isEmpty(sortOrder)) { if (TextUtils.isEmpty(sortOrder)) {
sortOrder = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC" sortOrder = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC"
+ "," + Tables.USER_IDS + "." + UserIds.RANK + " ASC"; + "," + Tables.USER_PACKETS + "." + UserPackets.RANK + " ASC";
} }
break; break;
@ -542,20 +552,24 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION);
projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER);
projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA); projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA);
projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); projectionMap.put(Certs.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID); projectionMap.put(Certs.SIGNER_UID, "signer." + UserPackets.USER_ID + " AS " + Certs.SIGNER_UID);
qb.setProjectionMap(projectionMap); qb.setProjectionMap(projectionMap);
qb.setTables(Tables.CERTS qb.setTables(Tables.CERTS
+ " JOIN " + Tables.USER_IDS + " ON (" + " JOIN " + Tables.USER_PACKETS + " ON ("
+ Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "
+ Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ " AND " + " AND "
+ Tables.CERTS + "." + Certs.RANK + " = " + Tables.CERTS + "." + Certs.RANK + " = "
+ Tables.USER_IDS + "." + UserIds.RANK + Tables.USER_PACKETS + "." + UserPackets.RANK
+ ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON (" // for now, we only return user ids here, so TYPE must be NULL
// TODO at some point, we should lift this restriction
+ " AND "
+ Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"
+ ") LEFT JOIN " + Tables.USER_PACKETS + " AS signer ON ("
+ Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = "
+ "signer." + UserIds.MASTER_KEY_ID + "signer." + UserPackets.MASTER_KEY_ID
+ " AND " + " AND "
+ "signer." + Keys.RANK + " = 0" + "signer." + Keys.RANK + " = 0"
+ ")"); + ")");
@ -662,8 +676,18 @@ public class KeychainProvider extends ContentProvider {
break; break;
case KEY_RING_USER_IDS: case KEY_RING_USER_IDS:
db.insertOrThrow(Tables.USER_IDS, null, values); // iff TYPE is null, user_id MUST be null as well
keyId = values.getAsLong(UserIds.MASTER_KEY_ID); if ( ! (values.get(UserPacketsColumns.TYPE) == null
? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null)
: (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null)
)) {
throw new AssertionError("Incorrect type for user packet! This is a bug!");
}
if (values.get(UserPacketsColumns.RANK) == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
throw new AssertionError("Rank 0 user packet must be a user id!");
}
db.insertOrThrow(Tables.USER_PACKETS, null, values);
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break; break;
case KEY_RING_CERTS: case KEY_RING_CERTS:

View File

@ -31,6 +31,8 @@ import android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
@ -53,7 +55,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@ -439,18 +440,18 @@ public class ProviderHelper {
// classify and order user ids. primary are moved to the front, revoked to the back, // classify and order user ids. primary are moved to the front, revoked to the back,
// otherwise the order in the keyfile is preserved. // otherwise the order in the keyfile is preserved.
List<UserPacketItem> uids = new ArrayList<UserPacketItem>();
if (trustedKeys.size() == 0) { if (trustedKeys.size() == 0) {
log(LogType.MSG_IP_UID_CLASSIFYING_ZERO); log(LogType.MSG_IP_UID_CLASSIFYING_ZERO);
} else { } else {
log(LogType.MSG_IP_UID_CLASSIFYING, trustedKeys.size()); log(LogType.MSG_IP_UID_CLASSIFYING, trustedKeys.size());
} }
mIndent += 1; mIndent += 1;
List<UserIdItem> uids = new ArrayList<UserIdItem>(); for (byte[] rawUserId : masterKey.getUnorderedRawUserIds()) {
for (byte[] rawUserId : new IterableIterator<byte[]>(
masterKey.getUnorderedRawUserIds().iterator())) {
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
UserIdItem item = new UserIdItem(); UserPacketItem item = new UserPacketItem();
uids.add(item); uids.add(item);
item.userId = userId; item.userId = userId;
@ -533,6 +534,105 @@ public class ProviderHelper {
} }
mIndent -= 1; mIndent -= 1;
ArrayList<WrappedUserAttribute> userAttributes = masterKey.getUnorderedUserAttributes();
// Don't spam the log if there aren't even any attributes
if ( ! userAttributes.isEmpty()) {
log(LogType.MSG_IP_UAT_CLASSIFYING);
}
mIndent += 1;
for (WrappedUserAttribute userAttribute : userAttributes) {
UserPacketItem item = new UserPacketItem();
uids.add(item);
item.type = userAttribute.getType();
item.attributeData = userAttribute.getEncoded();
int unknownCerts = 0;
switch (item.type) {
case WrappedUserAttribute.UAT_IMAGE:
log(LogType.MSG_IP_UAT_PROCESSING_IMAGE);
break;
default:
log(LogType.MSG_IP_UAT_PROCESSING_UNKNOWN);
break;
}
mIndent += 1;
// look through signatures for this specific key
for (WrappedSignature cert : new IterableIterator<WrappedSignature>(
masterKey.getSignaturesForUserAttribute(userAttribute))) {
long certId = cert.getKeyId();
// self signature
if (certId == masterKeyId) {
// NOTE self-certificates are already verified during canonicalization,
// AND we know there is at most one cert plus at most one revocation
if (!cert.isRevocation()) {
item.selfCert = cert;
} else {
item.isRevoked = true;
log(LogType.MSG_IP_UAT_REVOKED);
}
continue;
}
// do we have a trusted key for this?
if (trustedKeys.indexOfKey(certId) < 0) {
unknownCerts += 1;
continue;
}
// verify signatures from known private keys
CanonicalizedPublicKey trustedKey = trustedKeys.get(certId);
try {
cert.init(trustedKey);
// if it doesn't certify, leave a note and skip
if ( ! cert.verifySignature(masterKey, userAttribute)) {
log(LogType.MSG_IP_UAT_CERT_BAD);
continue;
}
log(cert.isRevocation()
? LogType.MSG_IP_UAT_CERT_GOOD_REVOKE
: LogType.MSG_IP_UAT_CERT_GOOD,
KeyFormattingUtils.convertKeyIdToHexShort(trustedKey.getKeyId())
);
// check if there is a previous certificate
WrappedSignature prev = item.trustedCerts.get(cert.getKeyId());
if (prev != null) {
// if it's newer, skip this one
if (prev.getCreationTime().after(cert.getCreationTime())) {
log(LogType.MSG_IP_UAT_CERT_OLD);
continue;
}
// if the previous one was a non-revokable certification, no need to look further
if (!prev.isRevocation() && !prev.isRevokable()) {
log(LogType.MSG_IP_UAT_CERT_NONREVOKE);
continue;
}
log(LogType.MSG_IP_UAT_CERT_NEW);
}
item.trustedCerts.put(cert.getKeyId(), cert);
} catch (PgpGeneralException e) {
log(LogType.MSG_IP_UAT_CERT_ERROR,
KeyFormattingUtils.convertKeyIdToHex(cert.getKeyId()));
}
}
if (unknownCerts > 0) {
log(LogType.MSG_IP_UAT_CERTS_UNKNOWN, unknownCerts);
}
mIndent -= 1;
}
mIndent -= 1;
progress.setProgress(LogType.MSG_IP_UID_REORDER.getMsgId(), 65, 100); progress.setProgress(LogType.MSG_IP_UID_REORDER.getMsgId(), 65, 100);
log(LogType.MSG_IP_UID_REORDER); log(LogType.MSG_IP_UID_REORDER);
// primary before regular before revoked (see UserIdItem.compareTo) // primary before regular before revoked (see UserIdItem.compareTo)
@ -540,7 +640,7 @@ public class ProviderHelper {
Collections.sort(uids); Collections.sort(uids);
// iterate and put into db // iterate and put into db
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
UserIdItem item = uids.get(userIdRank); UserPacketItem item = uids.get(userIdRank);
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
if (item.selfCert != null) { if (item.selfCert != null) {
// TODO get rid of "self verified" status? this cannot even happen anymore! // TODO get rid of "self verified" status? this cannot even happen anymore!
@ -605,15 +705,23 @@ public class ProviderHelper {
} }
private static class UserIdItem implements Comparable<UserIdItem> { private static class UserPacketItem implements Comparable<UserPacketItem> {
Integer type;
String userId; String userId;
byte[] attributeData;
boolean isPrimary = false; boolean isPrimary = false;
boolean isRevoked = false; boolean isRevoked = false;
WrappedSignature selfCert; WrappedSignature selfCert;
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<WrappedSignature>(); LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<WrappedSignature>();
@Override @Override
public int compareTo(UserIdItem o) { public int compareTo(UserPacketItem o) {
// if one is a user id, but the other isn't, the user id always comes first.
// we compare for null values here, so != is the correct operator!
// noinspection NumberEquality
if (type != o.type) {
return type == null ? -1 : 1;
}
// if one key is primary but the other isn't, the primary one always comes first // if one key is primary but the other isn't, the primary one always comes first
if (isPrimary != o.isPrimary) { if (isPrimary != o.isPrimary) {
return isPrimary ? -1 : 1; return isPrimary ? -1 : 1;
@ -1234,15 +1342,17 @@ public class ProviderHelper {
* Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
*/ */
private ContentProviderOperation private ContentProviderOperation
buildUserIdOperations(long masterKeyId, UserIdItem item, int rank) { buildUserIdOperations(long masterKeyId, UserPacketItem item, int rank) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(UserIds.MASTER_KEY_ID, masterKeyId); values.put(UserPackets.MASTER_KEY_ID, masterKeyId);
values.put(UserIds.USER_ID, item.userId); values.put(UserPackets.TYPE, item.type);
values.put(UserIds.IS_PRIMARY, item.isPrimary); values.put(UserPackets.USER_ID, item.userId);
values.put(UserIds.IS_REVOKED, item.isRevoked); values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
values.put(UserIds.RANK, rank); values.put(UserPackets.IS_PRIMARY, item.isPrimary);
values.put(UserPackets.IS_REVOKED, item.isRevoked);
values.put(UserPackets.RANK, rank);
Uri uri = UserIds.buildUserIdsUri(masterKeyId); Uri uri = UserPackets.buildUserIdsUri(masterKeyId);
return ContentProviderOperation.newInsert(uri).withValues(values).build(); return ContentProviderOperation.newInsert(uri).withValues(values).build();
} }

View File

@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.service;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
@ -49,6 +50,7 @@ public class SaveKeyringParcel implements Parcelable {
public ChangeUnlockParcel mNewUnlock; public ChangeUnlockParcel mNewUnlock;
public ArrayList<String> mAddUserIds; public ArrayList<String> mAddUserIds;
public ArrayList<WrappedUserAttribute> mAddUserAttribute;
public ArrayList<SubkeyAdd> mAddSubKeys; public ArrayList<SubkeyAdd> mAddSubKeys;
public ArrayList<SubkeyChange> mChangeSubKeys; public ArrayList<SubkeyChange> mChangeSubKeys;
@ -71,6 +73,7 @@ public class SaveKeyringParcel implements Parcelable {
public void reset() { public void reset() {
mNewUnlock = null; mNewUnlock = null;
mAddUserIds = new ArrayList<String>(); mAddUserIds = new ArrayList<String>();
mAddUserAttribute = new ArrayList<WrappedUserAttribute>();
mAddSubKeys = new ArrayList<SubkeyAdd>(); mAddSubKeys = new ArrayList<SubkeyAdd>();
mChangePrimaryUserId = null; mChangePrimaryUserId = null;
mChangeSubKeys = new ArrayList<SubkeyChange>(); mChangeSubKeys = new ArrayList<SubkeyChange>();
@ -162,6 +165,7 @@ public class SaveKeyringParcel implements Parcelable {
mNewUnlock = source.readParcelable(getClass().getClassLoader()); mNewUnlock = source.readParcelable(getClass().getClassLoader());
mAddUserIds = source.createStringArrayList(); mAddUserIds = source.createStringArrayList();
mAddUserAttribute = (ArrayList<WrappedUserAttribute>) source.readSerializable();
mAddSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable(); mAddSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
mChangeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable(); mChangeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
@ -184,6 +188,7 @@ public class SaveKeyringParcel implements Parcelable {
destination.writeParcelable(mNewUnlock, 0); destination.writeParcelable(mNewUnlock, 0);
destination.writeStringList(mAddUserIds); destination.writeStringList(mAddUserIds);
destination.writeSerializable(mAddUserAttribute);
destination.writeSerializable(mAddSubKeys); destination.writeSerializable(mAddSubKeys);
destination.writeSerializable(mChangeSubKeys); destination.writeSerializable(mChangeSubKeys);
@ -214,6 +219,7 @@ public class SaveKeyringParcel implements Parcelable {
String out = "mMasterKeyId: " + mMasterKeyId + "\n"; String out = "mMasterKeyId: " + mMasterKeyId + "\n";
out += "mNewUnlock: " + mNewUnlock + "\n"; out += "mNewUnlock: " + mNewUnlock + "\n";
out += "mAddUserIds: " + mAddUserIds + "\n"; out += "mAddUserIds: " + mAddUserIds + "\n";
out += "mAddUserAttribute: " + mAddUserAttribute + "\n";
out += "mAddSubKeys: " + mAddSubKeys + "\n"; out += "mAddSubKeys: " + mAddSubKeys + "\n";
out += "mChangeSubKeys: " + mChangeSubKeys + "\n"; out += "mChangeSubKeys: " + mChangeSubKeys + "\n";
out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n"; out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n";

View File

@ -49,7 +49,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
@ -82,11 +82,11 @@ public class CertifyKeyFragment extends LoaderFragment
private long mSignMasterKeyId = Constants.key.none; private long mSignMasterKeyId = Constants.key.none;
public static final String[] USER_IDS_PROJECTION = new String[]{ public static final String[] USER_IDS_PROJECTION = new String[]{
UserIds._ID, UserPackets._ID,
UserIds.MASTER_KEY_ID, UserPackets.MASTER_KEY_ID,
UserIds.USER_ID, UserPackets.USER_ID,
UserIds.IS_PRIMARY, UserPackets.IS_PRIMARY,
UserIds.IS_REVOKED UserPackets.IS_REVOKED
}; };
private static final int INDEX_MASTER_KEY_ID = 1; private static final int INDEX_MASTER_KEY_ID = 1;
private static final int INDEX_USER_ID = 2; private static final int INDEX_USER_ID = 2;
@ -182,7 +182,7 @@ public class CertifyKeyFragment extends LoaderFragment
@Override @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri uri = UserIds.buildUserIdsUri(); Uri uri = UserPackets.buildUserIdsUri();
String selection, ids[]; String selection, ids[];
{ {
@ -196,15 +196,15 @@ public class CertifyKeyFragment extends LoaderFragment
} }
} }
// put together selection string // put together selection string
selection = UserIds.IS_REVOKED + " = 0" + " AND " selection = UserPackets.IS_REVOKED + " = 0" + " AND "
+ Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ " IN (" + placeholders + ")"; + " IN (" + placeholders + ")";
} }
return new CursorLoader(getActivity(), uri, return new CursorLoader(getActivity(), uri,
USER_IDS_PROJECTION, selection, ids, USER_IDS_PROJECTION, selection, ids,
Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC" Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC"
+ ", " + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC" + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC"
); );
} }

View File

@ -47,6 +47,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
@ -334,7 +335,7 @@ public class EditKeyFragment extends LoaderFragment implements
switch (id) { switch (id) {
case LOADER_ID_USER_IDS: { case LOADER_ID_USER_IDS: {
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, return new CursorLoader(getActivity(), baseUri,
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
} }

View File

@ -37,10 +37,10 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
@ -199,7 +199,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
} }
case LOADER_ID_USER_IDS: { case LOADER_ID_USER_IDS: {
Uri baseUri = UserIds.buildUserIdsUri(mDataUri); Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, return new CursorLoader(getActivity(), baseUri,
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null); UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
} }

View File

@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -32,10 +31,9 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@ -47,12 +45,12 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
private SaveKeyringParcel mSaveKeyringParcel; private SaveKeyringParcel mSaveKeyringParcel;
public static final String[] USER_IDS_PROJECTION = new String[]{ public static final String[] USER_IDS_PROJECTION = new String[]{
UserIds._ID, UserPackets._ID,
UserIds.USER_ID, UserPackets.USER_ID,
UserIds.RANK, UserPackets.RANK,
UserIds.VERIFIED, UserPackets.VERIFIED,
UserIds.IS_PRIMARY, UserPackets.IS_PRIMARY,
UserIds.IS_REVOKED UserPackets.IS_REVOKED
}; };
private static final int INDEX_ID = 0; private static final int INDEX_ID = 0;
private static final int INDEX_USER_ID = 1; private static final int INDEX_USER_ID = 1;

View File

@ -35,6 +35,7 @@ import android.util.Patterns;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
@ -57,10 +58,10 @@ public class ContactHelper {
KeychainContract.KeyRings.EXPIRY, KeychainContract.KeyRings.EXPIRY,
KeychainContract.KeyRings.IS_REVOKED}; KeychainContract.KeyRings.IS_REVOKED};
public static final String[] USER_IDS_PROJECTION = new String[]{ public static final String[] USER_IDS_PROJECTION = new String[]{
KeychainContract.UserIds.USER_ID UserPackets.USER_ID
}; };
public static final String NON_REVOKED_SELECTION = KeychainContract.UserIds.IS_REVOKED + "=0"; public static final String NON_REVOKED_SELECTION = UserPackets.IS_REVOKED + "=0";
public static final String[] ID_PROJECTION = new String[]{ContactsContract.RawContacts._ID}; public static final String[] ID_PROJECTION = new String[]{ContactsContract.RawContacts._ID};
public static final String[] SOURCE_ID_PROJECTION = new String[]{ContactsContract.RawContacts.SOURCE_ID}; public static final String[] SOURCE_ID_PROJECTION = new String[]{ContactsContract.RawContacts.SOURCE_ID};
@ -413,7 +414,7 @@ public class ContactHelper {
int rawContactId, long masterKeyId) { int rawContactId, long masterKeyId) {
ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI),
rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build()); rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build());
Cursor ids = resolver.query(KeychainContract.UserIds.buildUserIdsUri(masterKeyId), Cursor ids = resolver.query(UserPackets.buildUserIdsUri(masterKeyId),
USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null); USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null);
if (ids != null) { if (ids != null) {
while (ids.moveToNext()) { while (ids.moveToNext()) {

View File

@ -309,6 +309,7 @@
<string name="progress_modify_unlock">"unlocking keyring…"</string> <string name="progress_modify_unlock">"unlocking keyring…"</string>
<string name="progress_modify_adduid">"adding user IDs…"</string> <string name="progress_modify_adduid">"adding user IDs…"</string>
<string name="progress_modify_adduat">"adding user attributes…"</string>
<string name="progress_modify_revokeuid">"revoking user IDs…"</string> <string name="progress_modify_revokeuid">"revoking user IDs…"</string>
<string name="progress_modify_primaryuid">"changing primary user ID…"</string> <string name="progress_modify_primaryuid">"changing primary user ID…"</string>
<string name="progress_modify_subkeychange">"modifying subkeys…"</string> <string name="progress_modify_subkeychange">"modifying subkeys…"</string>
@ -682,6 +683,23 @@
<string name="msg_ip_uid_reorder">"Re-ordering user IDs"</string> <string name="msg_ip_uid_reorder">"Re-ordering user IDs"</string>
<string name="msg_ip_uid_processing">"Processing user ID %s"</string> <string name="msg_ip_uid_processing">"Processing user ID %s"</string>
<string name="msg_ip_uid_revoked">"User ID is revoked"</string> <string name="msg_ip_uid_revoked">"User ID is revoked"</string>
<string name="msg_ip_uat_processing_image">"Processing user attribute of type image"</string>
<string name="msg_ip_uat_processing_unknown">"Processing user attribute of unknown type"</string>
<string name="msg_ip_uat_cert_bad">"Encountered bad certificate!"</string>
<string name="msg_ip_uat_cert_error">"Error processing certificate!"</string>
<string name="msg_ip_uat_cert_nonrevoke">"Already have a non-revokable certificate, skipping."</string>
<string name="msg_ip_uat_cert_old">"Certificate is older than previous, skipping."</string>
<string name="msg_ip_uat_cert_new">"Certificate is more recent, replacing previous."</string>
<string name="msg_ip_uat_cert_good">"Found good certificate by %1$s"</string>
<string name="msg_ip_uat_cert_good_revoke">"Found good certificate revocation by %1$s"</string>
<plurals name="msg_ip_uat_certs_unknown">
<item quantity="one">"Ignoring one certificate issued by an unknown public key"</item>
<item quantity="other">"Ignoring %s certificates issued by unknown public keys"</item>
</plurals>
<string name="msg_ip_uat_classifying">"Classifying user attributes"</string>
<string name="msg_ip_uat_revoked">"User attribute is revoked"</string>
<string name="msg_is_bad_type_public">"Tried to import public keyring as secret. This is a bug, please file a report!"</string> <string name="msg_is_bad_type_public">"Tried to import public keyring as secret. This is a bug, please file a report!"</string>
<string name="msg_is_bad_type_uncanon">"Tried to import a keyring without canonicalization. This is a bug, please file a report!"</string> <string name="msg_is_bad_type_uncanon">"Tried to import a keyring without canonicalization. This is a bug, please file a report!"</string>
@ -760,8 +778,23 @@
<string name="msg_kc_uid_revoke_old">"Removing outdated revocation certificate for user ID '%s'"</string> <string name="msg_kc_uid_revoke_old">"Removing outdated revocation certificate for user ID '%s'"</string>
<string name="msg_kc_uid_no_cert">"No valid self-certificate found for user ID '%s', removing from ring"</string> <string name="msg_kc_uid_no_cert">"No valid self-certificate found for user ID '%s', removing from ring"</string>
<string name="msg_kc_uid_remove">"Removing invalid user ID '%s'"</string> <string name="msg_kc_uid_remove">"Removing invalid user ID '%s'"</string>
<string name="msg_kc_uid_dup">"Removing duplicate user ID '%s'. The secret key contained two of them. This may result in missing certificates!"</string> <string name="msg_kc_uid_dup">"Removing duplicate user ID '%s'. The keyring contained two of them. This may result in missing certificates!"</string>
<string name="msg_kc_uid_warn_encoding">"User id does not verify as UTF-8!"</string> <string name="msg_kc_uid_warn_encoding">"User id does not verify as UTF-8!"</string>
<string name="msg_kc_uat_jpeg">"Processing user attribute of type JPEG"</string>
<string name="msg_kc_uat_unknown">"Processing user attribute of unknown type"</string>
<string name="msg_kc_uat_bad_err">"Removing bad self certificate for user attribute"</string>
<string name="msg_kc_uat_bad_local">"Removing user attribute certificate with 'local' flag"</string>
<string name="msg_kc_uat_bad_time">"Removing user attribute with future timestamp"</string>
<string name="msg_kc_uat_bad_type">"Removing user attribute certificate of unknown type (%s)"</string>
<string name="msg_kc_uat_bad">"Removing bad self certificate for user attribute"</string>
<string name="msg_kc_uat_cert_dup">"Removing outdated self certificate for user attribute"</string>
<string name="msg_kc_uat_dup">"Removing duplicate user attribute. The keyring contained two of them. This may result in missing certificates!"</string>
<string name="msg_kc_uat_foreign">"Removing foreign user attribute certificate by"</string>
<string name="msg_kc_uat_revoke_dup">"Removing redundant revocation certificate for user attribute"</string>
<string name="msg_kc_uat_revoke_old">"Removing outdated revocation certificate for user attribute"</string>
<string name="msg_kc_uat_no_cert">"No valid self-certificate found for user attribute, removing from ring"</string>
<string name="msg_kc_uat_remove">"Removing invalid user attribute"</string>
<string name="msg_kc_uat_warn_encoding">"User id does not verify as UTF-8!"</string>
<!-- Keyring merging log entries --> <!-- Keyring merging log entries -->
<string name="msg_mg_error_secret_dummy">"New public subkey found, but secret subkey dummy generation is not supported!"</string> <string name="msg_mg_error_secret_dummy">"New public subkey found, but secret subkey dummy generation is not supported!"</string>
@ -824,6 +857,8 @@
<string name="msg_mf_uid_primary">"Changing primary user ID to %s"</string> <string name="msg_mf_uid_primary">"Changing primary user ID to %s"</string>
<string name="msg_mf_uid_revoke">"Revoking user ID %s"</string> <string name="msg_mf_uid_revoke">"Revoking user ID %s"</string>
<string name="msg_mf_uid_error_empty">"User ID must not be empty!"</string> <string name="msg_mf_uid_error_empty">"User ID must not be empty!"</string>
<string name="msg_mf_uat_add_image">"Adding user attribute of type image"</string>
<string name="msg_mf_uat_add_unknown">"Adding user attribute of unknown type"</string>
<string name="msg_mf_unlock_error">"Error unlocking keyring!"</string> <string name="msg_mf_unlock_error">"Error unlocking keyring!"</string>
<string name="msg_mf_unlock">"Unlocking keyring"</string> <string name="msg_mf_unlock">"Unlocking keyring"</string>

2
extern/spongycastle vendored

@ -1 +1 @@
Subproject commit 1d9ee197d8fcc18dcdd1ae9649a5dc53e910b18c Subproject commit f13d9c202f7470ede75f2c02cc8c578acd3acee9