diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java index 122dd961..a46dd211 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java @@ -108,12 +108,21 @@ public final class ArmorUtils { } private static Tuple getPrimaryUserIdAndUserIdCount(PGPPublicKey publicKey) { + // Quickly determine the primary user-id + number of total user-ids + // NOTE: THIS METHOD DOES NOT CRYPTOGRAPHICALLY VERIFY THE SIGNATURES + // DO NOT RELY ON IT! Iterator userIds = publicKey.getUserIDs(); int countIdentities = 0; + String first = null; String primary = null; while (userIds.hasNext()) { countIdentities++; String userId = userIds.next(); + // remember the first user-id + if (first == null) { + first = userId; + } + if (primary == null) { Iterator signatures = publicKey.getSignaturesForID(userId); while (signatures.hasNext()) { @@ -125,7 +134,10 @@ public final class ArmorUtils { } } } - return new Tuple<>(primary, countIdentities); + // It may happen that no user-id is marked as primary + // in that case print the first one + String printed = primary != null ? primary : first; + return new Tuple<>(printed, countIdentities); } private static MultiMap keyToHeader(PGPPublicKey publicKey) { @@ -133,16 +145,24 @@ public final class ArmorUtils { OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(publicKey); header.put(HEADER_COMMENT, fingerprint.prettyPrint()); - Tuple idCount = getPrimaryUserIdAndUserIdCount(publicKey); - if (idCount.getA() != null) { - header.put(HEADER_COMMENT, idCount.getA() + " (Primary)"); - } - if (idCount.getB() != 1) { - header.put(HEADER_COMMENT, String.format("Public key contains %d identities", idCount.getB())); - } + setUserIdInfoOnHeader(header, publicKey); return header; } + private static void setUserIdInfoOnHeader(MultiMap header, PGPPublicKey publicKey) { + Tuple idCount = getPrimaryUserIdAndUserIdCount(publicKey); + String primary = idCount.getA(); + int totalCount = idCount.getB(); + if (primary != null) { + header.put(HEADER_COMMENT, primary); + } + if (totalCount == 2) { + header.put(HEADER_COMMENT, "1 further identity"); + } else if (totalCount > 2) { + header.put(HEADER_COMMENT, String.format("%d further identities", totalCount - 1)); + } + } + private static MultiMap keysToHeader(PGPKeyRing keyRing) { PGPPublicKey publicKey = keyRing.getPublicKey(); return keyToHeader(publicKey); diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java index d9ac2ca5..c320c649 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java @@ -163,10 +163,27 @@ public class ArmorUtilsTest { PGPPublicKey publicKey = secretKeyRing.getPublicKey(); PGPPublicKeyRing publicKeyRing = PGPainless.readKeyRing().publicKeyRing(publicKey.getEncoded()); String armored = PGPainless.asciiArmor(publicKeyRing); - Assertions.assertTrue(armored.contains("Comment: Juliet (Primary)")); - Assertions.assertTrue(armored.contains("Comment: Public key contains 2 identities")); + Assertions.assertTrue(armored.contains("Comment: Juliet ")); + Assertions.assertTrue(armored.contains("Comment: 1 further identity")); } + @Test + public void testEvenMoreIdentitiesInHeader() throws Exception { + PGPSecretKeyRing secretKeyRing = PGPainless.buildKeyRing() + .setPrimaryKey(KeySpec.getBuilder(ECDSA.fromCurve(EllipticCurve._P256), KeyFlag.SIGN_DATA, KeyFlag.CERTIFY_OTHER)) + .addUserId("Juliet ") + .addUserId("xmpp:juliet@capulet.lit") + .addUserId("Juliet Montague ") + .setPassphrase(Passphrase.fromPassword("test")) + .build(); + PGPPublicKey publicKey = secretKeyRing.getPublicKey(); + PGPPublicKeyRing publicKeyRing = PGPainless.readKeyRing().publicKeyRing(publicKey.getEncoded()); + String armored = PGPainless.asciiArmor(publicKeyRing); + Assertions.assertTrue(armored.contains("Comment: Juliet ")); + Assertions.assertTrue(armored.contains("Comment: 2 further identities")); + } + + @Test public void testSingleIdentityInHeader() throws Exception { PGPSecretKeyRing secretKeyRing = PGPainless.buildKeyRing() @@ -177,13 +194,13 @@ public class ArmorUtilsTest { PGPPublicKey publicKey = secretKeyRing.getPublicKey(); PGPPublicKeyRing publicKeyRing = PGPainless.readKeyRing().publicKeyRing(publicKey.getEncoded()); String armored = PGPainless.asciiArmor(publicKeyRing); - Assertions.assertTrue(armored.contains("Comment: Juliet (Primary)")); - Assertions.assertFalse(armored.contains("Comment: Public key contains 1 identities")); + Assertions.assertTrue(armored.contains("Comment: Juliet ")); + Assertions.assertFalse(armored.contains("Comment: 1 total identities")); } @Test public void testWithoutIdentityInHeader() throws Exception { - PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing("-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + "\n" + "xsBNBGIgzE0BCACwxaYg6bpmp0POq1T6yalGE9XaL2IG9d9khDBweZ63s3Pu1pHB\n" + "JtmjgN7Tx3ts6hLzQm3YKYA6zu1MXQ8k2vqtdtGUpZPp18Pbars7yUDqh8QIdFjO\n" + @@ -192,11 +209,13 @@ public class ArmorUtilsTest { "36qZ5ehAgz9MthPQINnZKpnqidqkGFvjwVFlCMlVSmNCNJmpgGDH3gvkklZHzGsf\n" + "dfzQswd/BQjPsFH9cK+QFYMG6q2zrvM0X9mdABEBAAE=\n" + "=njg8\n" + - "-----END PGP PUBLIC KEY BLOCK-----\n"); + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(CERT); PGPPublicKey publicKey = publicKeys.getPublicKey(); PGPPublicKeyRing publicKeyRing = PGPainless.readKeyRing().publicKeyRing(publicKey.getEncoded()); String armored = PGPainless.asciiArmor(publicKeyRing); - Assertions.assertTrue(armored.contains("Comment: Public key contains 0 identities")); + Assertions.assertFalse(armored.contains("Comment: 0 total identities")); } @TestTemplate