diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java')
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java | 237 |
1 files changed, 209 insertions, 28 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index f22ea7697..e1ce62bdf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -27,10 +27,14 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.TreeSet; import java.util.Vector; /** Wrapper around PGPKeyRing class, to be constructed from bytes. @@ -50,24 +54,27 @@ import java.util.Vector; * @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey * */ +@SuppressWarnings("unchecked") public class UncachedKeyRing { final PGPKeyRing mRing; final boolean mIsSecret; + final boolean mIsCanonicalized; UncachedKeyRing(PGPKeyRing ring) { mRing = ring; mIsSecret = ring instanceof PGPSecretKeyRing; + mIsCanonicalized = false; } - public long getMasterKeyId() { - return mRing.getPublicKey().getKeyID(); + private UncachedKeyRing(PGPKeyRing ring, boolean canonicalized) { + mRing = ring; + mIsSecret = ring instanceof PGPSecretKeyRing; + mIsCanonicalized = canonicalized; } - /* TODO don't use this */ - @Deprecated - public PGPKeyRing getRing() { - return mRing; + public long getMasterKeyId() { + return mRing.getPublicKey().getKeyID(); } public UncachedPublicKey getPublicKey() { @@ -94,6 +101,10 @@ public class UncachedKeyRing { return mIsSecret; } + public boolean isCanonicalized() { + return mIsCanonicalized; + } + public byte[] getEncoded() throws IOException { return mRing.getEncoded(); } @@ -102,15 +113,6 @@ public class UncachedKeyRing { return mRing.getPublicKey().getFingerprint(); } - public static UncachedKeyRing decodePublicFromData(byte[] data) - throws PgpGeneralException, IOException { - UncachedKeyRing ring = decodeFromData(data); - if(ring.isSecret()) { - throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!"); - } - return ring; - } - public static UncachedKeyRing decodeFromData(byte[] data) throws PgpGeneralException, IOException { BufferedInputStream bufferedInput = @@ -178,7 +180,7 @@ public class UncachedKeyRing { return result; } - /** "Canonicalizes" a key, removing inconsistencies in the process. This operation can be + /** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be * applied to public keyrings only. * * More specifically: @@ -193,19 +195,18 @@ public class UncachedKeyRing { * - certifications and certification revocations for user ids * - If a subkey retains no valid subkey binding certificate, remove it * - If a user id retains no valid self certificate, remove it + * - If the key is a secret key, remove all certificates by foreign keys + * - If no valid user id remains, log an error and return null * * This operation writes an OperationLog which can be used as part of a OperationResultParcel. * - * @return A canonicalized key + * @return A canonicalized key, or null on fatal error * */ + @SuppressWarnings("ConstantConditions") public UncachedKeyRing canonicalize(OperationLog log, int indent) { - if (isSecret()) { - throw new RuntimeException("Tried to canonicalize non-secret keyring. " + - "This is a programming error and should never happen!"); - } - log.add(LogLevel.START, LogType.MSG_KC, + log.add(LogLevel.START, isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC, new String[]{PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())}, indent); indent += 1; @@ -213,7 +214,7 @@ public class UncachedKeyRing { int redundantCerts = 0, badCerts = 0; - PGPPublicKeyRing ring = (PGPPublicKeyRing) mRing; + PGPKeyRing ring = mRing; PGPPublicKey masterKey = mRing.getPublicKey(); final long masterKeyId = masterKey.getKeyID(); @@ -334,8 +335,15 @@ public class UncachedKeyRing { continue; } - // If this is a foreign signature, never mind any further + // 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(LogLevel.WARN, LogType.MSG_KC_UID_FOREIGN, + new String[] { PgpKeyHelper.convertKeyIdToHex(certId) }, indent); + modified = PGPPublicKey.removeCertification(modified, userId, zert); + badCerts += 1; + } continue; } @@ -433,7 +441,7 @@ public class UncachedKeyRing { } // Replace modified key in the keyring - ring = PGPPublicKeyRing.insertPublicKey(ring, modified); + ring = replacePublicKey(ring, modified); indent -= 1; } @@ -578,7 +586,7 @@ public class UncachedKeyRing { // it is not properly bound? error! if (selfCert == null) { - ring = PGPPublicKeyRing.removePublicKey(ring, modified); + ring = replacePublicKey(ring, modified); log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT, new String[]{ PgpKeyHelper.convertKeyIdToHex(key.getKeyID()) }, indent); @@ -593,7 +601,7 @@ public class UncachedKeyRing { modified = PGPPublicKey.addCertification(modified, revocation); } // replace pubkey in keyring - ring = PGPPublicKeyRing.insertPublicKey(ring, modified); + ring = replacePublicKey(ring, modified); indent -= 1; } @@ -611,8 +619,181 @@ public class UncachedKeyRing { log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, null, indent); } - return new UncachedKeyRing(ring); + return new UncachedKeyRing(ring, true); + } + + /** This operation merges information from a different keyring, returning a combined + * UncachedKeyRing. + * + * The combined keyring contains the subkeys and user ids of both input 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 + * @return A consolidated UncachedKeyRing with the data of both input keyrings. Same type as + * this object, or null on error. + * + */ + public UncachedKeyRing merge(UncachedKeyRing other, OperationLog log, int indent) { + + log.add(LogLevel.DEBUG, isSecret() ? LogType.MSG_MG_SECRET : LogType.MSG_MG_PUBLIC, + new String[]{ PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()) }, indent); + indent += 1; + + long masterKeyId = other.getMasterKeyId(); + + 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 { + PGPKeyRing result = mRing; + PGPKeyRing candidate = other.mRing; + + // Pre-load all existing certificates + for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(result.getPublicKeys())) { + for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) { + certs.add(cert.getEncoded()); + } + } + + // keep track of the number of new certs we add + int newCerts = 0; + + 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; + } + + // Modifiable version of the old key, which we merge stuff into (keep old for comparison) + PGPPublicKey modified = resultKey; + + // Iterate certifications + for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) { + 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; + } + + // 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; + } + certs.add(encoded); + modified = PGPPublicKey.addCertification(modified, cert); + newCerts += 1; + } + + // If this is a subkey, merge it in and stop here + 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))) { + // 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, 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); + + } catch (IOException e) { + log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, null, indent); + return null; + } + + } + + public UncachedKeyRing extractPublicKeyRing() { + if(!isSecret()) { + throw new RuntimeException("Tried to extract public keyring from non-secret keyring. " + + "This is a programming error and should never happen!"); + } + + ArrayList<PGPPublicKey> keys = new ArrayList(); + Iterator<PGPPublicKey> it = mRing.getPublicKeys(); + while (it.hasNext()) { + keys.add(it.next()); + } + + return new UncachedKeyRing(new PGPPublicKeyRing(keys)); } + /** This method replaces a public key in a keyring. + * + * This method essentially wraps PGP*KeyRing.insertPublicKey, where the keyring may be of either + * the secret or public subclass. + * + * @return the resulting PGPKeyRing of the same type as the input + */ + private static PGPKeyRing replacePublicKey(PGPKeyRing ring, PGPPublicKey key) { + if (ring instanceof PGPPublicKeyRing) { + return PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) ring, key); + } + PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring; + 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); + return PGPSecretKeyRing.insertSecretKey(secRing, sKey); + } } |