From d4731f68bd30a762d8ab8ea4b53976dbf76704b3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 21 Nov 2016 15:37:24 +0100 Subject: [PATCH] introduce minimize extra to ACTION_GET_KEY --- .../keychain/pgp/CanonicalizedKeyRing.java | 13 +++ .../pgp/CanonicalizedPublicKeyRing.java | 72 +++++++++++++- .../keychain/pgp/PGPPublicKeyUtils.java | 99 +++++++++++++++++++ .../keychain/pgp/UncachedPublicKey.java | 24 ++++- .../keychain/remote/OpenPgpService.java | 5 + extern/openpgp-api-lib | 2 +- 6 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index 8f026561c..9f7c80ffc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -62,6 +62,10 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return getRing().getPublicKey().getFingerprint(); } + public byte[] getRawPrimaryUserId() throws PgpKeyNotFoundException { + return getPublicKey().getRawPrimaryUserId(); + } + public String getPrimaryUserId() throws PgpKeyNotFoundException { return getPublicKey().getPrimaryUserId(); } @@ -136,6 +140,15 @@ public abstract class CanonicalizedKeyRing extends KeyRing { } } + public long getSigningId() throws PgpKeyNotFoundException { + for(CanonicalizedPublicKey key : publicKeyIterator()) { + if (key.canSign() && key.isValid()) { + return key.getKeyId(); + } + } + throw new PgpKeyNotFoundException("No valid signing key found!"); + } + public void encode(OutputStream stream) throws IOException { getRing().encode(stream); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 1868b27d5..93b819af6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -18,6 +18,13 @@ package org.sufficientlysecure.keychain.pgp; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import android.support.annotation.Nullable; + import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -27,8 +34,6 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; -import java.io.IOException; -import java.util.Iterator; public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { @@ -84,6 +89,69 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { }); } + /** Returns a minimized version of this key. + * + * The minimized version includes: + * - the master key + * - the current best signing key (if any) + * - one encryption key (if any) + * - the user id that matches the userIdToKeep parameter, or the primary user id if none matches + * each with their most recent binding certificates + */ + public CanonicalizedPublicKeyRing minimize(@Nullable String userIdToKeep) throws IOException, PgpKeyNotFoundException { + CanonicalizedPublicKey masterKey = getPublicKey(); + PGPPublicKey masterPubKey = masterKey.getPublicKey(); + boolean userIdStrippedOk = false; + if (userIdToKeep != null) { + try { + masterPubKey = PGPPublicKeyUtils.keepOnlyUserId(masterPubKey, userIdToKeep); + userIdStrippedOk = true; + } catch (NoSuchElementException e) { + // will be handled because userIdStrippedOk is false + } + } + + if (!userIdStrippedOk) { + byte[] rawPrimaryUserId = getRawPrimaryUserId(); + masterPubKey = PGPPublicKeyUtils.keepOnlyRawUserId(masterPubKey, rawPrimaryUserId); + } + + masterPubKey = PGPPublicKeyUtils.keepOnlySelfCertsForUserIds(masterPubKey); + masterPubKey = PGPPublicKeyUtils.removeAllUserAttributes(masterPubKey); + masterPubKey = PGPPublicKeyUtils.removeAllDirectKeyCerts(masterPubKey); + + PGPPublicKeyRing resultRing = new PGPPublicKeyRing(masterPubKey.getEncoded(), new JcaKeyFingerprintCalculator()); + + Long encryptId; + try { + encryptId = getEncryptId(); + // only add if this key doesn't coincide with master key + if (encryptId != getMasterKeyId()) { + CanonicalizedPublicKey encryptKey = getPublicKey(encryptId); + PGPPublicKey encryptPubKey = encryptKey.getPublicKey(); + resultRing = PGPPublicKeyRing.insertPublicKey(resultRing, encryptPubKey); + } + } catch (PgpKeyNotFoundException e) { + // no encryption key: can't be reasonably minimized + return null; + } + + try { + long signingId = getSigningId(); + // only add if this key doesn't coincide with master or encryption key + if (signingId != encryptId && signingId != getMasterKeyId()) { + CanonicalizedPublicKey signingKey = getPublicKey(signingId); + PGPPublicKey signingPubKey = signingKey.getPublicKey(); + resultRing = PGPPublicKeyRing.insertPublicKey(resultRing, signingPubKey); + } + } catch (PgpKeyNotFoundException e) { + // no signing key: can't be reasonably minimized + return null; + } + + return new CanonicalizedPublicKeyRing(resultRing, getVerified()); + } + /** Create a dummy secret ring from this key */ public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) { PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java new file mode 100644 index 000000000..a3909aff8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PGPPublicKeyUtils.java @@ -0,0 +1,99 @@ +package org.sufficientlysecure.keychain.pgp; + + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +import org.sufficientlysecure.keychain.util.Utf8Util; + + +@SuppressWarnings("unchecked") // BouncyCastle doesn't do generics here :( +class PGPPublicKeyUtils { + + static PGPPublicKey keepOnlyRawUserId(PGPPublicKey masterPublicKey, byte[] rawUserIdToKeep) { + boolean elementToKeepFound = false; + + Iterator it = masterPublicKey.getRawUserIDs(); + while (it.hasNext()) { + byte[] rawUserId = it.next(); + if (Arrays.equals(rawUserId, rawUserIdToKeep)) { + elementToKeepFound = true; + } else { + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, rawUserId); + } + } + + if (!elementToKeepFound) { + throw new NoSuchElementException(); + } + return masterPublicKey; + } + + static PGPPublicKey keepOnlyUserId(PGPPublicKey masterPublicKey, String userIdToKeep) { + boolean elementToKeepFound = false; + + Iterator it = masterPublicKey.getRawUserIDs(); + while (it.hasNext()) { + byte[] rawUserId = it.next(); + String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); + if (userId.contains(userIdToKeep)) { + elementToKeepFound = true; + } else { + masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, rawUserId); + } + } + + if (!elementToKeepFound) { + throw new NoSuchElementException(); + } + return masterPublicKey; + } + + static PGPPublicKey keepOnlySelfCertsForUserIds(PGPPublicKey masterPubKey) { + long masterKeyId = masterPubKey.getKeyID(); + + Iterator it = masterPubKey.getRawUserIDs(); + while (it.hasNext()) { + byte[] rawUserId = it.next(); + masterPubKey = keepOnlySelfCertsForRawUserId(masterPubKey, masterKeyId, rawUserId); + } + + return masterPubKey; + } + + private static PGPPublicKey keepOnlySelfCertsForRawUserId( + PGPPublicKey masterPubKey, long masterKeyId, byte[] rawUserId) { + Iterator it = masterPubKey.getSignaturesForID(rawUserId); + while (it.hasNext()) { + PGPSignature sig = it.next(); + if (sig.getKeyID() != masterKeyId) { + masterPubKey = PGPPublicKey.removeCertification(masterPubKey, rawUserId, sig); + } + } + return masterPubKey; + } + + static PGPPublicKey removeAllUserAttributes(PGPPublicKey masterPubKey) { + Iterator it = masterPubKey.getUserAttributes(); + + while (it.hasNext()) { + masterPubKey = PGPPublicKey.removeCertification(masterPubKey, it.next()); + } + + return masterPubKey; + } + + static PGPPublicKey removeAllDirectKeyCerts(PGPPublicKey masterPubKey) { + Iterator it = masterPubKey.getSignaturesOfType(PGPSignature.DIRECT_KEY); + + while (it.hasNext()) { + masterPubKey = PGPPublicKey.removeCertification(masterPubKey, it.next()); + } + + return masterPubKey; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 77ce0dc90..4305d8a12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -105,6 +105,24 @@ public class UncachedPublicKey { * */ public String getPrimaryUserId() { + byte[] found = getRawPrimaryUserId(); + if (found != null) { + return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found); + } else { + return null; + } + } + + /** Returns the primary user id, as indicated by the public key's self certificates. + * + * This is an expensive operation, since potentially a lot of certificates (and revocations) + * have to be checked, and even then the result is NOT guaranteed to be constant through a + * canonicalization operation. + * + * Returns null if there is no primary user id (as indicated by certificates) + * + */ + public byte[] getRawPrimaryUserId() { byte[] found = null; PGPSignature foundSig = null; // noinspection unchecked @@ -161,11 +179,7 @@ public class UncachedPublicKey { } } } - if (found != null) { - return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found); - } else { - return null; - } + return found; } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 42d0b1490..bec9ef501 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -544,6 +544,11 @@ public class OpenPgpService extends Service { Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + if (data.getBooleanExtra(OpenPgpApi.EXTRA_MINIMIZE, false)) { + String userIdToKeep = data.getStringExtra(OpenPgpApi.EXTRA_MINIMIZE_USER_ID); + keyRing = keyRing.minimize(userIdToKeep); + } + boolean requestedKeyData = outputStream != null; if (requestedKeyData) { boolean requestAsciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false); diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index 395fe837a..182852fce 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit 395fe837a23fc33c7a4abbb0625cb584c5dbb176 +Subproject commit 182852fcec5d62024e8e5fb2c84272cae63a8ba0