aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
diff options
context:
space:
mode:
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.java237
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);
+ }
}