consolidate: make it work

This commit is contained in:
Vincent Breitmoser 2014-06-18 13:43:18 +02:00
parent 1e45e5cd9a
commit f80228a08d
4 changed files with 127 additions and 106 deletions

View File

@ -27,11 +27,13 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Arrays; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector; import java.util.Vector;
/** Wrapper around PGPKeyRing class, to be constructed from bytes. /** Wrapper around PGPKeyRing class, to be constructed from bytes.
@ -190,10 +192,6 @@ public class UncachedKeyRing {
*/ */
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public UncachedKeyRing canonicalize(OperationLog log, int indent) { public UncachedKeyRing canonicalize(OperationLog log, int indent) {
if (isSecret()) {
throw new RuntimeException("Tried to public-canonicalize non-public keyring. " +
"This is a programming error and should never happen!");
}
log.add(LogLevel.START, isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC, log.add(LogLevel.START, isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC,
new String[]{PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())}, indent); new String[]{PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())}, indent);
@ -611,126 +609,130 @@ public class UncachedKeyRing {
return new UncachedKeyRing(ring); return new UncachedKeyRing(ring);
} }
/** This operation consolidates a list of UncachedKeyRings into a single, combined /** This operation merges information from a different keyring, returning a combined
* UncachedKeyRing. * UncachedKeyRing.
* *
* The combined keyring contains the subkeys and user ids of all input keyrings. Even if all * The combined keyring contains the subkeys and user ids of both input keyrings, but it does
* input keyrings were canonicalized at some point, the resulting keyring will not necessarily * not necessarily have the canonicalized property.
* have that property.
* *
* TODO work with secret keys * @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
* @param list The list of UncachedKeyRings. Must not be empty, and all of the same masterKeyId * this object, or null on error.
* @return A consolidated UncachedKeyRing with the data of all input keyrings.
* *
*/ */
public static UncachedKeyRing consolidate(List<UncachedKeyRing> list, public UncachedKeyRing merge(UncachedKeyRing other, OperationLog log, int indent) {
OperationLog log, int indent) {
long masterKeyId = list.get(0).getMasterKeyId(); log.add(LogLevel.START, isSecret() ? LogType.MSG_MG_SECRET : LogType.MSG_MG_PUBLIC,
new String[]{ PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()) }, indent);
log.add(LogLevel.START, LogType.MSG_KO,
new String[]{
Integer.toString(list.size()),
PgpKeyHelper.convertKeyIdToHex(masterKeyId)
}, indent);
indent += 1; indent += 1;
// remember which certs we already added long masterKeyId = other.getMasterKeyId();
HashSet<Integer> certs = new HashSet<Integer>();
if (getMasterKeyId() != masterKeyId) {
log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, null, indent);
return null;
}
// remember which certs we already added. this is cheaper than semantic deduplication
Set<byte[]> certs = new TreeSet<byte[]>(new Comparator<byte[]>() {
public int compare(byte[] left, byte[] right) {
// check for length equality
if (left.length != right.length) {
return left.length - right.length;
}
// compare byte-by-byte
for (int i = 0; i < left.length && i < right.length; i++) {
if (left[i] != right[i]) {
return (left[i] & 0xff) - (right[i] & 0xff);
}
}
// ok they're the same
return 0;
}});
try { try {
PGPPublicKeyRing result = null; PGPKeyRing result = mRing;
int num = 1; PGPKeyRing candidate = other.mRing;
for (UncachedKeyRing uring : new IterableIterator<UncachedKeyRing>(list.iterator())) {
PGPPublicKeyRing ring = (PGPPublicKeyRing) uring.mRing; // Pre-load all existing certificates
if (uring.getMasterKeyId() != masterKeyId) { for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(result.getPublicKeys())) {
log.add(LogLevel.ERROR, LogType.MSG_KO_HETEROGENEOUS, null, indent); for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) {
return null; certs.add(cert.getEncoded());
} }
}
// If this is the first ring, just take it // keep track of the number of new certs we add
if (result == null) { int newCerts = 0;
result = ring;
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(candidate.getPublicKeys())) {
final PGPPublicKey resultKey = result.getPublicKey(key.getKeyID());
if (resultKey == null) {
log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, null, indent);
result = replacePublicKey(result, key);
continue; continue;
} }
log.add(LogLevel.DEBUG, LogType.MSG_KO_MERGING, // Modifiable version of the old key, which we merge stuff into (keep old for comparison)
new String[] { Integer.toString(num++) }, indent); PGPPublicKey modified = resultKey;
indent += 1;
// keep track of the number of new certs we add // Iterate certifications
int newCerts = 0; for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) {
int type = cert.getSignatureType();
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) { // Disregard certifications on user ids, we will deal with those later
if (type == PGPSignature.NO_CERTIFICATION
final PGPPublicKey resultkey = result.getPublicKey(key.getKeyID()); || type == PGPSignature.DEFAULT_CERTIFICATION
if (resultkey == null) { || type == PGPSignature.CASUAL_CERTIFICATION
log.add(LogLevel.DEBUG, LogType.MSG_KO_NEW_SUBKEY, null, indent); || type == PGPSignature.POSITIVE_CERTIFICATION
result = PGPPublicKeyRing.insertPublicKey(result, key); || type == PGPSignature.CERTIFICATION_REVOCATION) {
continue; continue;
} }
// The key old key, which we merge stuff into byte[] encoded = cert.getEncoded();
PGPPublicKey modified = resultkey; // Known cert, skip it
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) { if (certs.contains(encoded)) {
int type = cert.getSignatureType();
// Disregard certifications on user ids, we will deal with those later
if (type == PGPSignature.NO_CERTIFICATION
|| type == PGPSignature.DEFAULT_CERTIFICATION
|| type == PGPSignature.CASUAL_CERTIFICATION
|| type == PGPSignature.POSITIVE_CERTIFICATION
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
continue;
}
int hash = Arrays.hashCode(cert.getEncoded());
// Known cert, skip it
if (certs.contains(hash)) {
continue;
}
certs.add(hash);
modified = PGPPublicKey.addCertification(modified, cert);
newCerts += 1;
}
// If this is a subkey, stop here
if (!key.isMasterKey()) {
result = PGPPublicKeyRing.insertPublicKey(result, modified);
continue; continue;
} }
certs.add(encoded);
// Copy over all user id certificates modified = PGPPublicKey.addCertification(modified, cert);
for (String userId : new IterableIterator<String>(key.getUserIDs())) { newCerts += 1;
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignaturesForID(userId))) {
int hash = Arrays.hashCode(cert.getEncoded());
// Known cert, skip it
if (certs.contains(hash)) {
continue;
}
newCerts += 1;
certs.add(hash);
modified = PGPPublicKey.addCertification(modified, userId, cert);
}
}
// If anything changed, save the updated (sub)key
if (modified != resultkey) {
result = PGPPublicKeyRing.insertPublicKey(result, modified);
}
} }
log.add(LogLevel.DEBUG, LogType.MSG_KO_FOUND_NEW, // If this is a subkey, merge it in and stop here
new String[] { Integer.toString(newCerts) }, indent); if (!key.isMasterKey()) {
if (modified != resultKey) {
result = replacePublicKey(result, modified);
}
continue;
}
// Copy over all user id certificates
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignaturesForID(userId))) {
byte[] encoded = cert.getEncoded();
// Known cert, skip it
if (certs.contains(encoded)) {
continue;
}
newCerts += 1;
certs.add(encoded);
modified = PGPPublicKey.addCertification(modified, userId, cert);
}
}
// If anything changed, save the updated (sub)key
if (modified != resultKey) {
result = replacePublicKey(result, modified);
}
} }
log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW,
new String[] { Integer.toString(newCerts) }, indent);
return new UncachedKeyRing(result); return new UncachedKeyRing(result);
} catch (IOException e) { } catch (IOException e) {
log.add(LogLevel.ERROR, LogType.MSG_KO_FATAL_ENCODE, null, indent); log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, null, indent);
return null; return null;
} }
@ -749,6 +751,10 @@ public class UncachedKeyRing {
} }
PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring; PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring;
PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID()); PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID());
// TODO generate secret key with S2K dummy, if none exists! for now, just die.
if (sKey == null) {
throw new RuntimeException("dummy secret key generation not yet implemented");
}
sKey = PGPSecretKey.replacePublicKey(sKey, key); sKey = PGPSecretKey.replacePublicKey(sKey, key);
return PGPSecretKeyRing.insertSecretKey(secRing, sKey); return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
} }

View File

@ -677,6 +677,19 @@ public class ProviderHelper {
secretRing = getWrappedSecretKeyRing(keyRing.getMasterKeyId()).getUncached(); secretRing = getWrappedSecretKeyRing(keyRing.getMasterKeyId()).getUncached();
log(LogLevel.DEBUG, LogType.MSG_IP_PRESERVING_SECRET); log(LogLevel.DEBUG, LogType.MSG_IP_PRESERVING_SECRET);
progress.setProgress(LogType.MSG_IP_PRESERVING_SECRET.getMsgId(), 10, 100); progress.setProgress(LogType.MSG_IP_PRESERVING_SECRET.getMsgId(), 10, 100);
mIndent += 1;
// Merge data from new public ring into secret one
secretRing = secretRing.merge(keyRing, mLog, mIndent);
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
// NOTE that the info from this secret keyring will implicitly be merged into the
// new public keyring, since that one is merged with the old public keyring.
mIndent -= 1;
} catch (NotFoundException e) { } catch (NotFoundException e) {
secretRing = null; secretRing = null;
} }

View File

@ -222,12 +222,12 @@ public class OperationResultParcel implements Parcelable {
MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old), MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old),
// keyring consolidation // keyring consolidation
MSG_KO (R.string.msg_ko), MSG_MG_PUBLIC (R.string.msg_mg_public),
MSG_KO_FATAL_ENCODE (R.string.msg_ko_fatal_encode), MSG_MG_SECRET (R.string.msg_mg_secret),
MSG_KO_HETEROGENEOUS (R.string.msg_ko_heterogeneous), MSG_MG_FATAL_ENCODE (R.string.msg_mg_fatal_encode),
MSG_KO_MERGING (R.string.msg_ko_merging), MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous),
MSG_KO_NEW_SUBKEY (R.string.msg_ko_new_subkey), MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),
MSG_KO_FOUND_NEW (R.string.msg_ko_found_new), MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
; ;
private final int mMsgId; private final int mMsgId;

View File

@ -598,12 +598,14 @@
<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_ko">Consolidating %1$s keyrings into %2$s</string>
<string name="msg_ko_fatal_encode">Fatal error encoding signature</string> <!-- Keyring merging log entries -->
<string name="msg_ko_heterogeneous">Tried to consolidate heterogeneous keyrings</string> <string name="msg_mg_public">Merging into secret keyring %s</string>
<string name="msg_ko_merging">Merging keyring #%s</string> <string name="msg_mg_secret">Merging into public keyring %s</string>
<string name="msg_ko_new_subkey">Adding new subkey %s</string> <string name="msg_mg_fatal_encode">Fatal error encoding signature</string>
<string name="msg_ko_found_new">Found %s new certificates in keyring</string> <string name="msg_mg_heterogeneous">Tried to consolidate heterogeneous keyrings</string>
<string name="msg_mg_new_subkey">Adding new subkey %s</string>
<string name="msg_mg_found_new">Found %s new certificates in keyring</string>
<!-- unsorted --> <!-- unsorted -->
<string name="section_certifier_id">Certifier</string> <string name="section_certifier_id">Certifier</string>