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 | 126 |
1 files changed, 98 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 b343c779a..a445e161f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -41,7 +41,6 @@ import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Utf8Util; -import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -138,7 +137,7 @@ public class UncachedKeyRing { public static UncachedKeyRing decodeFromData(byte[] data) throws PgpGeneralException, IOException { - Iterator<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data)); + IteratorWithIOThrow<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data)); if ( ! parsed.hasNext()) { throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); @@ -154,14 +153,14 @@ public class UncachedKeyRing { } - public static Iterator<UncachedKeyRing> fromStream(final InputStream stream) throws IOException { + public static IteratorWithIOThrow<UncachedKeyRing> fromStream(final InputStream stream) { - return new Iterator<UncachedKeyRing>() { + return new IteratorWithIOThrow<UncachedKeyRing>() { UncachedKeyRing mNext = null; PGPObjectFactory mObjectFactory = null; - private void cacheNext() { + private void cacheNext() throws IOException { if (mNext != null) { return; } @@ -190,21 +189,19 @@ public class UncachedKeyRing { // if we are past the while loop, that means the objectFactory had no next mObjectFactory = null; } - } catch (IOException e) { - Log.e(Constants.TAG, "IOException while processing stream. ArmoredInputStream CRC check failed?", e); } catch (ArrayIndexOutOfBoundsException e) { - Log.e(Constants.TAG, "ArmoredInputStream decode failed, symbol is not in decodingTable!", e); + throw new IOException(e); } } @Override - public boolean hasNext() { + public boolean hasNext() throws IOException { cacheNext(); return mNext != null; } @Override - public UncachedKeyRing next() { + public UncachedKeyRing next() throws IOException { try { cacheNext(); return mNext; @@ -212,15 +209,15 @@ public class UncachedKeyRing { mNext = null; } } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } }; } + public interface IteratorWithIOThrow<E> { + public boolean hasNext() throws IOException; + public E next() throws IOException; + } + public void encodeArmored(OutputStream out, String version) throws IOException { ArmoredOutputStream aos = new ArmoredOutputStream(out); if (version != null) { @@ -267,6 +264,35 @@ public class UncachedKeyRing { */ @SuppressWarnings("ConstantConditions") public CanonicalizedKeyRing canonicalize(OperationLog log, int indent) { + return canonicalize(log, indent, false); + } + + + /** "Canonicalizes" a public key, removing inconsistencies in the process. + * + * 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, + * including revocations with later re-certifications. + * - Remove all certificates in other positions if not of known type: + * - key revocation signatures on the master key + * - subkey binding signatures for subkeys + * - 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 an OperationResultParcel. + * + * @param forExport if this is true, non-exportable signatures will be removed + * @return A canonicalized key, or null on fatal error (log will include a message in this case) + * + */ + @SuppressWarnings("ConstantConditions") + public CanonicalizedKeyRing canonicalize(OperationLog log, int indent, boolean forExport) { log.add(isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC, indent, KeyFormattingUtils.convertKeyIdToHex(getMasterKeyId())); @@ -302,6 +328,7 @@ public class UncachedKeyRing { PGPPublicKey modified = masterKey; PGPSignature revocation = null; + PGPSignature notation = null; for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) { int type = zert.getSignatureType(); @@ -311,16 +338,16 @@ public class UncachedKeyRing { || type == PGPSignature.CASUAL_CERTIFICATION || type == PGPSignature.POSITIVE_CERTIFICATION || type == PGPSignature.CERTIFICATION_REVOCATION) { - log.add(LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent); + log.add(LogType.MSG_KC_MASTER_BAD_TYPE_UID, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } WrappedSignature cert = new WrappedSignature(zert); - if (type != PGPSignature.KEY_REVOCATION) { + if (type != PGPSignature.KEY_REVOCATION && type != PGPSignature.DIRECT_KEY) { // Unknown type, just remove - log.add(LogType.MSG_KC_REVOKE_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); + log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -328,30 +355,63 @@ public class UncachedKeyRing { if (cert.getCreationTime().after(nowPlusOneDay)) { // Creation date in the future? No way! - log.add(LogType.MSG_KC_REVOKE_BAD_TIME, indent); + log.add(LogType.MSG_KC_MASTER_BAD_TIME, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - if (cert.isLocal()) { - // Remove revocation certs with "local" flag - log.add(LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); + try { + cert.init(masterKey); + if (!cert.verifySignature(masterKey)) { + log.add(LogType.MSG_KC_MASTER_BAD, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + } catch (PgpGeneralException e) { + log.add(LogType.MSG_KC_MASTER_BAD_ERR, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - try { - cert.init(masterKey); - if (!cert.verifySignature(masterKey)) { - log.add(LogType.MSG_KC_REVOKE_BAD, indent); + // if this is for export, we always remove any non-exportable certs + if (forExport && cert.isLocal()) { + // Remove revocation certs with "local" flag + log.add(LogType.MSG_KC_MASTER_LOCAL, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + continue; + } + + // special case: non-exportable, direct key signatures for notations! + if (cert.getSignatureType() == PGPSignature.DIRECT_KEY) { + // must be local, otherwise strip! + if (!cert.isLocal()) { + log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - } catch (PgpGeneralException e) { - log.add(LogType.MSG_KC_REVOKE_BAD_ERR, indent); + + // first notation? fine then. + if (notation == null) { + notation = zert; + // more notations? at least one is superfluous, then. + } else if (notation.getCreationTime().before(zert.getCreationTime())) { + log.add(LogType.MSG_KC_NOTATION_DUP, indent); + modified = PGPPublicKey.removeCertification(modified, notation); + redundantCerts += 1; + notation = zert; + } else { + log.add(LogType.MSG_KC_NOTATION_DUP, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + redundantCerts += 1; + } + continue; + } else if (cert.isLocal()) { + // Remove revocation certs with "local" flag + log.add(LogType.MSG_KC_MASTER_BAD_LOCAL, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -373,6 +433,16 @@ public class UncachedKeyRing { } } + // If we have a notation packet, check if there is even any data in it? + if (notation != null) { + // If there isn't, might as well strip it + if (new WrappedSignature(notation).getNotation().isEmpty()) { + log.add(LogType.MSG_KC_NOTATION_EMPTY, indent); + modified = PGPPublicKey.removeCertification(modified, notation); + redundantCerts += 1; + } + } + ArrayList<String> processedUserIds = new ArrayList<String>(); for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); |