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.java418
1 files changed, 417 insertions, 1 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 1264c8c36..e309ed632 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -2,14 +2,23 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.S2K;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.openpgp.PGPKeyFlags;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
+import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
+import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -18,7 +27,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
+import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -169,4 +178,411 @@ public class UncachedKeyRing {
return result;
}
+ /** "Canonicalizes" a key, removing inconsistencies in the process. This operation can be
+ * applied to public keyrings only.
+ *
+ * More specifically:
+ * - Remove all non-verifying self-certificates
+ * - Remove all "future" self-certificates
+ * - Remove all certificates flagged as "local"
+ * - Remove all certificates which are superseded by a newer one on the same target
+ *
+ * After this cleaning, a number of checks are done: TODO implement
+ * - See if each subkey retains a valid self certificate
+ * - See if each user id retains a valid self certificate
+ *
+ * This operation writes an OperationLog which can be used as part of a OperationResultParcel.
+ *
+ * @return A canonicalized key
+ *
+ */
+ 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,
+ new String[]{PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())}, indent);
+ indent += 1;
+
+ final Date now = new Date();
+
+ int removedCerts = 0;
+
+ PGPPublicKeyRing ring = (PGPPublicKeyRing) mRing;
+ PGPPublicKey masterKey = mRing.getPublicKey();
+ final long masterKeyId = masterKey.getKeyID();
+
+ {
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER,
+ new String[]{PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())}, indent);
+ indent += 1;
+
+ PGPPublicKey modified = masterKey;
+ PGPSignature revocation = null;
+ for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getSignatures())) {
+ int type = zert.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;
+ }
+ WrappedSignature cert = new WrappedSignature(zert);
+
+ if (type != PGPSignature.KEY_REVOCATION) {
+ // Unknown type, just remove
+ log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE, new String[]{
+ "0x" + Integer.toString(type, 16)
+ }, indent);
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ continue;
+ }
+
+ if (cert.getCreationTime().after(now)) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent);
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ continue;
+ }
+
+ if (cert.isLocal()) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent);
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ continue;
+ }
+
+ try {
+ cert.init(masterKey);
+ if (!cert.verifySignature(masterKey)) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD, null, indent);
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ continue;
+ }
+ } catch (PgpGeneralException e) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_ERR, null, indent);
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ continue;
+ }
+
+ // first revocation? fine then.
+ if (revocation == null) {
+ revocation = zert;
+ // more revocations? at least one is superfluous, then.
+ } else if (revocation.getCreationTime().before(zert.getCreationTime())) {
+ modified = PGPPublicKey.removeCertification(modified, revocation);
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent);
+ revocation = zert;
+ } else {
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent);
+ }
+ }
+
+ for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ PGPSignature selfCert = null;
+ revocation = null;
+
+ // look through signatures for this specific key
+ for (PGPSignature zert : new IterableIterator<PGPSignature>(
+ masterKey.getSignaturesForID(userId))) {
+ 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(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TYPE,
+ new String[] {
+ "0x" + Integer.toString(zert.getSignatureType(), 16)
+ }, indent);
+ modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ removedCerts += 1;
+ }
+
+ if (cert.getCreationTime().after(now)) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent);
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ continue;
+ }
+
+ if (cert.isLocal()) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent);
+ modified = PGPPublicKey.removeCertification(modified, zert);
+ removedCerts += 1;
+ continue;
+ }
+
+ // If this is a foreign signature, never mind any further
+ if (certId != masterKeyId) {
+ continue;
+ }
+
+ // Otherwise, first make sure it checks out
+ try {
+ cert.init(masterKey);
+ if (!cert.verifySignature(masterKey, userId)) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD,
+ new String[] { userId }, indent);
+ modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ removedCerts += 1;
+ continue;
+ }
+ } catch (PgpGeneralException e) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_ERR,
+ new String[] { userId }, indent);
+ modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ removedCerts += 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())) {
+ modified = PGPPublicKey.removeCertification(modified, userId, selfCert);
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_UID_DUP,
+ new String[] { userId }, indent);
+ selfCert = zert;
+ } else {
+ modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_UID_DUP,
+ new String[] { userId }, indent);
+ }
+ // If there is a revocation certificate, and it's older than this, drop it
+ if (revocation != null
+ && revocation.getCreationTime().before(selfCert.getCreationTime())) {
+ modified = PGPPublicKey.removeCertification(modified, userId, revocation);
+ revocation = null;
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_OLD,
+ new String[] { userId }, indent);
+ }
+ break;
+
+ case PGPSignature.CERTIFICATION_REVOCATION:
+ // If this is older than the (latest) self cert, drop it
+ if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
+ modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_OLD,
+ new String[] { userId }, indent);
+ 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())) {
+ modified = PGPPublicKey.removeCertification(modified, userId, revocation);
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_DUP,
+ new String[] { userId }, indent);
+ revocation = zert;
+ } else {
+ modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ removedCerts += 1;
+ log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_DUP,
+ new String[] { userId }, indent);
+ }
+ break;
+
+ }
+
+ }
+ }
+
+ // Replace modified key in the keyring
+ ring = PGPPublicKeyRing.insertPublicKey(ring, modified);
+
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER_SUCCESS, null, indent);
+ indent -= 1;
+
+ }
+
+ // Process all keys
+ for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) {
+ // Don't care about the master key here, that one gets special treatment above
+ if (key.isMasterKey()) {
+ continue;
+ }
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB,
+ new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent);
+ indent += 1;
+ // A subkey needs exactly one subkey binding certificate, and optionally one revocation
+ // certificate.
+ PGPPublicKey modified = key;
+ PGPSignature selfCert = null, revocation = null;
+ uids: for (PGPSignature zig : new IterableIterator<PGPSignature>(key.getSignatures())) {
+ // remove from keyring (for now)
+ modified = PGPPublicKey.removeCertification(modified, zig);
+ // add this too, easier than adding it for every single "continue" case
+ removedCerts += 1;
+
+ WrappedSignature cert = new WrappedSignature(zig);
+ int type = cert.getSignatureType();
+
+ // filter out bad key types...
+ if (cert.getKeyId() != masterKey.getKeyID()) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_KEYID, null, indent);
+ continue;
+ }
+
+ if (type != PGPSignature.SUBKEY_BINDING && type != PGPSignature.SUBKEY_REVOCATION) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TYPE, new String[]{
+ "0x" + Integer.toString(type, 16)
+ }, indent);
+ continue;
+ }
+
+ if (cert.getCreationTime().after(now)) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TIME, null, indent);
+ continue;
+ }
+
+ if (cert.isLocal()) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_LOCAL, null, indent);
+ continue;
+ }
+
+ if (type == PGPSignature.SUBKEY_BINDING) {
+
+ // make sure the certificate checks out
+ try {
+ cert.init(masterKey);
+ if (!cert.verifySignature(masterKey, key)) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD, null, indent);
+ continue;
+ }
+ } catch (PgpGeneralException e) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_ERR, null, indent);
+ continue;
+ }
+
+ if (zig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
+ int flags = ((KeyFlags) zig.getHashedSubPackets()
+ .getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags();
+ // If this subkey is allowed to sign data,
+ if ((flags & PGPKeyFlags.CAN_SIGN) == PGPKeyFlags.CAN_SIGN) {
+ try {
+ PGPSignatureList list = zig.getUnhashedSubPackets().getEmbeddedSignatures();
+ boolean ok = false;
+ for (int i = 0; i < list.size(); i++) {
+ WrappedSignature subsig = new WrappedSignature(list.get(i));
+ if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
+ subsig.init(key);
+ if (subsig.verifySignature(masterKey, key)) {
+ ok = true;
+ } else {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD, null, indent);
+ continue uids;
+ }
+ }
+ }
+ if (!ok) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, null, indent);
+ continue;
+ }
+ } catch (Exception e) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, null, indent);
+ continue;
+ }
+ }
+ }
+
+ // if we already have a cert, and this one is not newer: skip it
+ if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) {
+ continue;
+ }
+
+ selfCert = zig;
+ // if this is newer than a possibly existing revocation, drop that one
+ if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) {
+ revocation = null;
+ }
+
+ // it must be a revocation, then (we made sure above)
+ } else {
+
+ // make sure the certificate checks out
+ try {
+ cert.init(masterKey);
+ if (!cert.verifySignature(key)) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, null, indent);
+ continue;
+ }
+ } catch (PgpGeneralException e) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD_ERR, null, indent);
+ continue;
+ }
+
+ // if there is no binding (yet), or the revocation is newer than the binding: keep it
+ if (selfCert == null || selfCert.getCreationTime().before(cert.getCreationTime())) {
+ revocation = zig;
+ }
+ }
+ }
+
+ // it is not properly bound? error!
+ if (selfCert == null) {
+ ring = PGPPublicKeyRing.removePublicKey(ring, modified);
+
+ log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT,
+ new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent);
+ indent -= 1;
+ continue;
+ }
+
+ // re-add certification
+ modified = PGPPublicKey.addCertification(modified, selfCert);
+ removedCerts -= 1;
+ // add revocation, if any
+ if (revocation != null) {
+ modified = PGPPublicKey.addCertification(modified, revocation);
+ removedCerts -= 1;
+ }
+ // replace pubkey in keyring
+ ring = PGPPublicKeyRing.insertPublicKey(ring, modified);
+
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_SUCCESS, null, indent);
+ indent -= 1;
+ }
+
+ if (removedCerts > 0) {
+ log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_REMOVED,
+ new String[] { Integer.toString(removedCerts) }, indent);
+ } else {
+ log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, null, indent);
+ }
+
+ return new UncachedKeyRing(ring);
+ }
+
+
}