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 | 418 |
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); + } + + } |