mirror of
https://github.com/moparisthebest/open-keychain
synced 2025-01-31 23:20:20 -05:00
consolidate: make it work
This commit is contained in:
parent
1e45e5cd9a
commit
f80228a08d
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user