diff --git a/pg/src/main/java/org/bouncycastle/bcpg/AEADEncDataPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/AEADEncDataPacket.java index 227276d938..f417e94d70 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/AEADEncDataPacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/AEADEncDataPacket.java @@ -67,6 +67,10 @@ public byte getVersion() return version; } + /** + * Return the algorithm-id of the symmetric encryption algorithm used to encrypt the data. + * @return symmetric encryption algorithm + */ public byte getAlgorithm() { return algorithm; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/KeyIdentifier.java b/pg/src/main/java/org/bouncycastle/openpgp/KeyIdentifier.java new file mode 100644 index 0000000000..1ea0c1193d --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/KeyIdentifier.java @@ -0,0 +1,345 @@ +package org.bouncycastle.openpgp; + +import org.bouncycastle.bcpg.FingerprintUtil; +import org.bouncycastle.bcpg.SignatureSubpacket; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.sig.IssuerFingerprint; +import org.bouncycastle.bcpg.sig.IssuerKeyID; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.util.List; + +/** + * Utility class for matching key-ids / fingerprints. + * A {@link KeyIdentifier} can be created from either a 64-bit key-id, a fingerprint, or both. + * This class was created to enable a seamless transition from use of key-ids in the API + * towards identifying keys via fingerprints. + */ +public class KeyIdentifier +{ + private final byte[] fingerprint; + private final long keyId; + + /** + * Create a new {@link KeyIdentifier} based on a keys fingerprint. + * For fingerprints matching the format of a v4, v5 or v6 key, the constructor will + * try to derive the corresponding key-id from the fingerprint. + * + * @param fingerprint fingerprint + */ + public KeyIdentifier(byte[] fingerprint) + { + this.fingerprint = Arrays.clone(fingerprint); + + // v4 + if (fingerprint.length == 20) + { + keyId = FingerprintUtil.keyIdFromV4Fingerprint(fingerprint); + } + // v5, v6 + else if (fingerprint.length == 32) + { + keyId = FingerprintUtil.keyIdFromV6Fingerprint(fingerprint); + } + else + { + keyId = 0L; + } + } + + /** + * Create a {@link KeyIdentifier} based on the given fingerprint and key-id. + * + * @param fingerprint fingerprint + * @param keyId key-id + */ + public KeyIdentifier(byte[] fingerprint, long keyId) + { + this.fingerprint = Arrays.clone(fingerprint); + this.keyId = keyId; + } + + /** + * Create a {@link KeyIdentifier} based on the given key-id. + * {@code fingerprint} will be set to {@code null}. + * + * @param keyId key-id + */ + public KeyIdentifier(long keyId) + { + this(null, keyId); + } + + /** + * Create a {@link KeyIdentifier} for the given {@link PGPPublicKey}. + * + * @param key key + */ + public KeyIdentifier(PGPPublicKey key) + { + this(key.getFingerprint(), key.getKeyID()); + } + + /** + * Create a {@link KeyIdentifier} for the given {@link PGPSecretKey}. + * + * @param key key + */ + public KeyIdentifier(PGPSecretKey key) + { + this(key.getPublicKey()); + } + + /** + * Create a {@link KeyIdentifier} for the given {@link PGPPrivateKey}. + * + * @param key key + * @param fingerprintCalculator calculate the fingerprint + * @throws PGPException if an exception happens while calculating the fingerprint + */ + public KeyIdentifier(PGPPrivateKey key, KeyFingerPrintCalculator fingerprintCalculator) + throws PGPException + { + this(new PGPPublicKey(key.getPublicKeyPacket(), fingerprintCalculator)); + } + + /** + * Create a wildcard {@link KeyIdentifier}. + */ + private KeyIdentifier() + { + this(new byte[0], 0L); + } + + /** + * Create a wildcard {@link KeyIdentifier}. + * + * @return wildcard key identifier + */ + public static KeyIdentifier wildcard() + { + return new KeyIdentifier(); + } + + /** + * Return the fingerprint of the {@link KeyIdentifier}. + * {@code fingerprint} might be null, if the {@link KeyIdentifier} was created from just a key-id. + * If {@link #isWildcard()} returns true, this method returns an empty, but non-null array. + * + * @return fingerprint + */ + public byte[] getFingerprint() + { + return fingerprint; + } + + /** + * Return the key-id of the {@link KeyIdentifier}. + * This might be {@code 0L} if {@link #isWildcard()} returns true, or if an unknown + * fingerprint was passed in. + * + * @return key-id + */ + public long getKeyId() + { + return keyId; + } + + /** + * Return true, if this {@link KeyIdentifier} matches the given {@link PGPPublicKey}. + * This will return true if the fingerprint matches, or if the key-id matches, + * or if {@link #isWildcard()} returns true. + * + * @param key key + * @return if the identifier matches the key + */ + public boolean matches(PGPPublicKey key) + { + if (isWildcard()) + { + return true; + } + + if (fingerprint != null) + { + return Arrays.constantTimeAreEqual(fingerprint, key.getFingerprint()); + } + else + { + return keyId == key.getKeyID(); + } + } + + /** + * Return true if this {@link KeyIdentifier} matches the given {@link PGPSecretKey}. + * This will return true if the fingerprint matches, or if the key-id matches, + * or if {@link #isWildcard()} returns true. + * + * @param key key + * @return whether the identifier matches the key + */ + public boolean matches(PGPSecretKey key) + { + return matches(key.getPublicKey()); + } + + /** + * Return true if this {@link KeyIdentifier} matches the given {@link PGPPrivateKey}. + * This will return true if the fingerprint matches, or if the key-id matches, + * or in case that {@link #isWildcard()} is true. + * + * @param key key + * @param fingerprintCalculator to calculate the fingerprint + * @return whether the identifier matches the key + * @throws PGPException if an exception happens while calculating the fingerprint + */ + public boolean matches(PGPPrivateKey key, + KeyFingerPrintCalculator fingerprintCalculator) + throws PGPException + { + return matches(new PGPPublicKey(key.getPublicKeyPacket(), fingerprintCalculator)); + } + + public boolean matches(PGPSignature sig) + { + if (isWildcard()) + { + return true; + } + + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + return matches(hashed) || matches(unhashed); + } + + private boolean matches(PGPSignatureSubpacketVector subpackets) + { + if (fingerprint != null) + { + for (SignatureSubpacket subpacket : subpackets.getSubpackets(SignatureSubpacketTags.ISSUER_FINGERPRINT)) + { + IssuerFingerprint issuer = (IssuerFingerprint) subpacket; + if (Arrays.constantTimeAreEqual(fingerprint, issuer.getFingerprint())) + { + return true; + } + // wildcard fingerprint + if (issuer.getFingerprint().length == 0) + { + return true; + } + } + } + + for (SignatureSubpacket subpacket : subpackets.getSubpackets(SignatureSubpacketTags.ISSUER_KEY_ID)) + { + IssuerKeyID issuer = (IssuerKeyID) subpacket; + if (issuer.getKeyID() == keyId) + { + return true; + } + // wildcard key-id + if (issuer.getKeyID() == 0) + { + return true; + } + } + + return false; + } + + /** + * Returns true, if the {@link KeyIdentifier} specifies a wildcard (matches anything). + * This is for example used with anonymous recipient key-ids / fingerprints, where the recipient + * needs to try all available keys to decrypt the message. + * + * @return is wildcard + */ + public boolean isWildcard() + { + return keyId == 0L && fingerprint.length == 0; + } + + /** + * Return true, if any of the {@link KeyIdentifier KeyIdentifiers} in the {@code identifiers} list + * matches the given {@link PGPPublicKey}. + * + * @param identifiers list of identifiers + * @param key key + * @return true if any matches, false if none matches + */ + public static boolean matches(List identifiers, PGPPublicKey key) + { + for (KeyIdentifier identifier : identifiers) + { + if (identifier.matches(key)) + { + return true; + } + } + return false; + } + + /** + * Return true, if any of the {@link KeyIdentifier KeyIdentifiers} in the {@code identifiers} list + * matches the given {@link PGPSecretKey}. + * + * @param identifiers list of identifiers + * @param key key + * @return true if any matches, false if none matches + */ + public static boolean matches(List identifiers, PGPSecretKey key) + { + for (KeyIdentifier identifier : identifiers) + { + if (identifier.matches(key)) + { + return true; + } + } + return false; + } + + /** + * Return true, if any of the {@link KeyIdentifier KeyIdentifiers} in the {@code identifiers} list + * matches the given {@link PGPPrivateKey}. + * + * @param identifiers list of identifiers + * @param key key + * @param fingerprintCalculator to calculate the fingerprint + * @return true if any matches, false if none matches + */ + public static boolean matches(List identifiers, + PGPPrivateKey key, + KeyFingerPrintCalculator fingerprintCalculator) + throws PGPException + { + for (KeyIdentifier identifier : identifiers) + { + if (identifier.matches(key, fingerprintCalculator)) + { + return true; + } + } + return false; + } + + public String toString() + { + if (isWildcard()) + { + return "*"; + } + + if (getFingerprint() == null) + { + return "" + keyId; + } + + // -DM Hex.toHexString + return Hex.toHexString(fingerprint).toUpperCase(); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java index 81c03f08c4..e0d600ec56 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java @@ -41,6 +41,16 @@ public long getKeyID() { return pub.getKeyID(); } + + /** + * Return the {@link KeyIdentifier} associated with the public key. + * + * @return key identifier + */ + public KeyIdentifier getKeyIdentifier() + { + return new KeyIdentifier(getPublicKey()); + } public PGPPublicKey getPublicKey() { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java index fb12d5c11d..a810b8cc65 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java @@ -125,6 +125,10 @@ static void readUserIDs( */ public abstract PGPPublicKey getPublicKey(byte[] fingerprint); + public abstract PGPPublicKey getPublicKey(KeyIdentifier identifier); + + public abstract Iterator getPublicKeys(KeyIdentifier identifier); + /** * Return an iterator containing all the public keys carrying signatures issued from key keyID. * @@ -132,6 +136,8 @@ static void readUserIDs( */ public abstract Iterator getKeysWithSignaturesBy(long keyID); + public abstract Iterator getKeysWithSignaturesBy(KeyIdentifier identifier); + /** * Return the number of keys in the key ring. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java index f7be3dee4f..bd99042b4e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java @@ -174,6 +174,16 @@ public byte[] getFingerprint() return sigPack.getFingerprint(); } + /** + * Return a {@link KeyIdentifier} identifying this {@link PGPOnePassSignature}. + * + * @return key identifier + */ + public KeyIdentifier getKeyIdentifier() + { + return new KeyIdentifier(getFingerprint(), getKeyID()); + } + /** * Return the salt used in the corresponding signature. * Only for {@link OnePassSignaturePacket#VERSION_6} packets. diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java index 2c788db3df..bcbdbe17b6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java @@ -395,6 +395,16 @@ public long getKeyID() return keyID; } + /** + * Return a {@link KeyIdentifier} identifying this key. + * + * @return key identifier + */ + public KeyIdentifier getKeyIdentifier() + { + return new KeyIdentifier(this); + } + /** * Return the fingerprint of the public key. * @@ -568,6 +578,20 @@ public Iterator getSignaturesForKeyID( return sigs.iterator(); } + public Iterator getSignaturesForKey(KeyIdentifier identifier) + { + List sigs = new ArrayList<>(); + for (Iterator it = getSignatures(); it.hasNext(); ) + { + PGPSignature sig = it.next(); + if (identifier.matches(sig)) + { + sigs.add(sig); + } + } + return sigs.iterator(); + } + private Iterator getSignaturesForID( UserIDPacket id) { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java index 792bd3ef19..8a9d74606d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java @@ -52,12 +52,24 @@ private boolean confirmCheckSum( * Return the keyID for the key used to encrypt the data. * * @return long + * @deprecated use {@link #getKeyIdentifier()} instead */ + @Deprecated public long getKeyID() { return keyData.getKeyID(); } + /** + * Return a {@link KeyIdentifier} for the key used to encrypt the data. + * + * @return key identifier + */ + public KeyIdentifier getKeyIdentifier() + { + return new KeyIdentifier(keyData.getKeyFingerprint(), keyData.getKeyID()); + } + /** * Return the symmetric key algorithm required to decrypt the data protected by this object. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java index 9cd941e224..2d19d78a52 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java @@ -194,6 +194,33 @@ public PGPPublicKey getPublicKey(byte[] fingerprint) return null; } + @Override + public PGPPublicKey getPublicKey(KeyIdentifier identifier) + { + for (PGPPublicKey k : keys) + { + if (identifier.matches(k)) + { + return k; + } + } + return null; + } + + @Override + public Iterator getPublicKeys(KeyIdentifier identifier) + { + List matches = new ArrayList<>(); + for (PGPPublicKey k : keys) + { + if (identifier.matches(k)) + { + matches.add(k); + } + } + return matches.iterator(); + } + /** * Return any keys carrying a signature issued by the key represented by keyID. * @@ -219,6 +246,21 @@ public Iterator getKeysWithSignaturesBy(long keyID) return keysWithSigs.iterator(); } + @Override + public Iterator getKeysWithSignaturesBy(KeyIdentifier identifier) + { + List keysWithSigs = new ArrayList<>(); + for (PGPPublicKey k : keys) + { + Iterator sigIt = k.getSignaturesForKey(identifier); + if (sigIt.hasNext()) + { + keysWithSigs.add(k); + } + } + return keysWithSigs.iterator(); + } + /** * Return an iterator containing all the public keys. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java index ed1e900759..e53ab7714e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java @@ -476,6 +476,16 @@ public long getKeyID() return pub.getKeyID(); } + /** + * Return a {@link KeyIdentifier} for this key. + * + * @return identifier + */ + public KeyIdentifier getKeyIdentifier() + { + return new KeyIdentifier(this); + } + /** * Return the fingerprint of the public key associated with this key. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java index d9427c26c3..de12c469c8 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java @@ -256,6 +256,74 @@ public PGPPublicKey getPublicKey(byte[] fingerprint) return null; } + @Override + public PGPPublicKey getPublicKey(KeyIdentifier identifier) + { + for (PGPSecretKey k : keys) + { + if (k.getPublicKey() != null && identifier.matches(k)) + { + return k.getPublicKey(); + } + } + + for (PGPPublicKey k : extraPubKeys) + { + if (identifier.matches(k)) + { + return k; + } + } + return null; + } + + @Override + public Iterator getPublicKeys(KeyIdentifier identifier) + { + List matches = new ArrayList<>(); + for (PGPSecretKey k : keys) + { + if (k.getPublicKey() != null && identifier.matches(k)) + { + matches.add(k.getPublicKey()); + } + } + + for (PGPPublicKey k : extraPubKeys) + { + if (identifier.matches(k)) + { + matches.add(k); + } + } + return matches.iterator(); + } + + public PGPSecretKey getSecretKey(KeyIdentifier identifier) + { + for (PGPSecretKey k : keys) + { + if (identifier.matches(k)) + { + return k; + } + } + return null; + } + + public Iterator getSecretKeys(KeyIdentifier identifier) + { + List matches = new ArrayList<>(); + for (PGPSecretKey k : keys) + { + if (identifier.matches(k)) + { + matches.add(k); + } + } + return matches.iterator(); + } + /** * Return any keys carrying a signature issued by the key represented by keyID. * @@ -281,6 +349,33 @@ public Iterator getKeysWithSignaturesBy(long keyID) return keysWithSigs.iterator(); } + @Override + public Iterator getKeysWithSignaturesBy(KeyIdentifier identifier) + { + List keysWithSigs = new ArrayList<>(); + for (PGPSecretKey k : keys) + { + if (k.getPublicKey() == null) + { + continue; + } + Iterator sigIt = k.getPublicKey().getSignaturesForKey(identifier); + if (sigIt.hasNext()) + { + keysWithSigs.add(k.getPublicKey()); + } + } + for (PGPPublicKey k : extraPubKeys) + { + Iterator sigIt = k.getSignaturesForKey(identifier); + if (sigIt.hasNext()) + { + keysWithSigs.add(k); + } + } + return keysWithSigs.iterator(); + } + /** * Return an iterator containing all the public keys. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java index c270d85b6d..a6ef810ff1 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java @@ -19,6 +19,8 @@ import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.TrustPacket; +import org.bouncycastle.bcpg.sig.IssuerFingerprint; +import org.bouncycastle.bcpg.sig.IssuerKeyID; import org.bouncycastle.math.ec.rfc8032.Ed25519; import org.bouncycastle.math.ec.rfc8032.Ed448; import org.bouncycastle.openpgp.operator.PGPContentVerifier; @@ -421,6 +423,10 @@ public int getSignatureType() /** * Return the id of the key that created the signature. + * Note: Since signatures of version 4 or later encode the issuer information inside a + * signature subpacket ({@link IssuerKeyID} or {@link IssuerFingerprint}), there is not + * a single source of truth for the key-id. + * To match any suitable issuer keys, use {@link #getKeyIdentifiers()} instead. * * @return keyID of the signatures corresponding key. */ @@ -429,6 +435,65 @@ public long getKeyID() return sigPck.getKeyID(); } + /** + * Create a list of {@link KeyIdentifier} objects, for all {@link IssuerFingerprint} + * and {@link IssuerKeyID} signature subpackets found in either the hashed or unhashed areas + * of the signature. + * + * @return all detectable {@link KeyIdentifier KeyIdentifiers} + */ + public List getKeyIdentifiers() + { + List identifiers = new ArrayList<>(); + identifiers.addAll(getHashedKeyIdentifiers()); + identifiers.addAll(getUnhashedKeyIdentifiers()); + return identifiers; + } + + /** + * Return a list of all {@link KeyIdentifier KeyIdentifiers} that could be derived from + * any {@link IssuerFingerprint} or {@link IssuerKeyID} subpackets of the hashed signature + * subpacket area. + * + * @return hashed key identifiers + */ + public List getHashedKeyIdentifiers() + { + return extractKeyIdentifiers(sigPck.getHashedSubPackets()); + } + + /** + * Return a list of all {@link KeyIdentifier KeyIdentifiers} that could be derived from + * any {@link IssuerFingerprint} or {@link IssuerKeyID} subpackets of the unhashed signature + * subpacket area. + * + * @return unhashed key identifiers + */ + public List getUnhashedKeyIdentifiers() + { + return extractKeyIdentifiers(sigPck.getUnhashedSubPackets()); + } + + private List extractKeyIdentifiers(SignatureSubpacket[] subpackets) + { + List identifiers = new ArrayList<>(); + for (SignatureSubpacket s : subpackets) + { + if (s instanceof IssuerFingerprint) + { + IssuerFingerprint issuer = (IssuerFingerprint) s; + identifiers.add(new KeyIdentifier(issuer.getFingerprint())); + } + + if (s instanceof IssuerKeyID) + { + IssuerKeyID issuer = (IssuerKeyID) s; + identifiers.add(new KeyIdentifier(issuer.getKeyID())); + } + } + return identifiers; + } + /** * Return the creation time of the signature. * diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/KeyIdentifierTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/KeyIdentifierTest.java new file mode 100644 index 0000000000..e8e982f27e --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/KeyIdentifierTest.java @@ -0,0 +1,272 @@ +package org.bouncycastle.openpgp.test; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.FingerprintUtil; +import org.bouncycastle.openpgp.KeyIdentifier; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; + +public class KeyIdentifierTest + extends SimpleTest +{ + @Override + public String getName() + { + return "KeyIdentifierTest"; + } + + @Override + public void performTest() + throws Exception + { + testWildcardIdentifier(); + testIdentifierFromKeyId(); + + testIdentifierFromV4Fingerprint(); + testIdentifierFromV6Fingerprint(); + + testMatchV4Key(); + testMatchV6Key(); + } + + private void testWildcardIdentifier() + { + KeyIdentifier wildcard = KeyIdentifier.wildcard(); + isEquals("Wildcard KeyIdentifier MUST have key-id 0", + 0L, wildcard.getKeyId()); + isTrue("Wildcard KeyIdentifier MUST have zero-length fingerprint", + Arrays.areEqual(new byte[0], wildcard.getFingerprint())); + isTrue("Wildcard MUST return true for isWildcard()", + wildcard.isWildcard()); + + isEquals("*", wildcard.toString()); + } + + private void testIdentifierFromKeyId() + { + KeyIdentifier identifier = new KeyIdentifier(1234L); + isEquals("Identifier key ID mismatch", + 1234L, identifier.getKeyId()); + isTrue("Identifier MUST return null for getFingerprint()", + identifier.getFingerprint() == null); + + isEquals("1234", identifier.toString()); + } + + private void testIdentifierFromV4Fingerprint() + { + String hexFingerprint = "D1A66E1A23B182C9980F788CFBFCC82A015E7330"; + byte[] fingerprint = Hex.decode(hexFingerprint); + KeyIdentifier identifier = new KeyIdentifier(fingerprint); + isTrue("Identifier fingerprint mismatch", + Arrays.areEqual(fingerprint, identifier.getFingerprint())); + isEquals("Identifier key-ID mismatch", + FingerprintUtil.keyIdFromV4Fingerprint(fingerprint), identifier.getKeyId()); + + isEquals(hexFingerprint, identifier.toString()); + } + + private void testIdentifierFromV6Fingerprint() + { + String hexFingerprint = "CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9"; + byte[] fingerprint = Hex.decode(hexFingerprint); + KeyIdentifier identifier = new KeyIdentifier(fingerprint); + isTrue("Identifier fingerprint mismatch", + Arrays.areEqual(fingerprint, identifier.getFingerprint())); + isEquals("Identifier key-ID mismatch", + FingerprintUtil.keyIdFromV6Fingerprint(fingerprint), identifier.getKeyId()); + + isEquals(hexFingerprint, identifier.toString()); + } + + private void testMatchV4Key() + throws IOException, PGPException + { + PGPSecretKeyRing secretKeys = getV4Key(); + Iterator it = secretKeys.getSecretKeys(); + PGPSecretKey primaryKey = it.next(); + PGPSecretKey subkey = it.next(); + + KeyIdentifier primaryIdentifier = primaryKey.getKeyIdentifier(); + isEquals(primaryKey.getKeyID(), primaryIdentifier.getKeyId()); + isTrue(Arrays.areEqual(primaryKey.getFingerprint(), primaryIdentifier.getFingerprint())); + isTrue(primaryIdentifier.matches(primaryKey)); + isTrue(primaryIdentifier.matches(primaryKey.getPublicKey())); + isTrue(primaryKey.getPublicKey().getKeyIdentifier().getKeyId()==primaryIdentifier.getKeyId()); + isTrue(!primaryIdentifier.matches(subkey)); + isTrue(!primaryIdentifier.matches(subkey.getPublicKey())); + + KeyIdentifier subkeyIdentifier = subkey.getKeyIdentifier(); + isEquals(subkey.getKeyID(), subkeyIdentifier.getKeyId()); + isTrue(Arrays.areEqual(subkey.getFingerprint(), subkeyIdentifier.getFingerprint())); + isTrue(subkeyIdentifier.matches(subkey)); + isTrue(subkeyIdentifier.matches(subkey.getPublicKey())); + isTrue(!subkeyIdentifier.matches(primaryKey)); + isTrue(!subkeyIdentifier.matches(primaryKey.getPublicKey())); + + PGPPrivateKey privateKey = primaryKey.extractPrivateKey(null); + KeyIdentifier privateKeyIdentifier = new KeyIdentifier(privateKey, new JcaKeyFingerprintCalculator()); + isTrue(privateKeyIdentifier.matches(privateKey, new JcaKeyFingerprintCalculator())); + isTrue(privateKeyIdentifier.matches(primaryKey)); + isTrue(primaryIdentifier.matches(privateKey, new JcaKeyFingerprintCalculator())); + isTrue(!subkeyIdentifier.matches(privateKey, new JcaKeyFingerprintCalculator())); + + KeyIdentifier wildcard = KeyIdentifier.wildcard(); + isTrue(wildcard.matches(primaryKey)); + isTrue(wildcard.matches(subkey)); + isTrue(wildcard.matches(privateKey, new JcaKeyFingerprintCalculator())); + + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + primaryKey)); + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + primaryKey.getPublicKey())); + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + subkey)); + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + subkey.getPublicKey())); + } + + private void testMatchV6Key() + throws IOException, PGPException + { + PGPSecretKeyRing secretKeys = getV6Key(); + Iterator it = secretKeys.getSecretKeys(); + PGPSecretKey primaryKey = it.next(); + PGPSecretKey subkey = it.next(); + + KeyIdentifier primaryIdentifier = primaryKey.getKeyIdentifier(); + isEquals(primaryKey.getKeyID(), primaryIdentifier.getKeyId()); + isTrue(Arrays.areEqual(primaryKey.getFingerprint(), primaryIdentifier.getFingerprint())); + isTrue(primaryIdentifier.matches(primaryKey)); + isTrue(primaryIdentifier.matches(primaryKey.getPublicKey())); + isTrue(!primaryIdentifier.matches(subkey)); + isTrue(!primaryIdentifier.matches(subkey.getPublicKey())); + + KeyIdentifier subkeyIdentifier = subkey.getKeyIdentifier(); + isEquals(subkey.getKeyID(), subkeyIdentifier.getKeyId()); + isTrue(Arrays.areEqual(subkey.getFingerprint(), subkeyIdentifier.getFingerprint())); + isTrue(subkeyIdentifier.matches(subkey)); + isTrue(subkeyIdentifier.matches(subkey.getPublicKey())); + isTrue(!subkeyIdentifier.matches(primaryKey)); + isTrue(!subkeyIdentifier.matches(primaryKey.getPublicKey())); + + PGPPrivateKey privateKey = primaryKey.extractPrivateKey(null); + KeyIdentifier privateKeyIdentifier = new KeyIdentifier(privateKey, new BcKeyFingerprintCalculator()); + isTrue(privateKeyIdentifier.matches(privateKey, new BcKeyFingerprintCalculator())); + isTrue(privateKeyIdentifier.matches(primaryKey)); + isTrue(primaryIdentifier.matches(privateKey, new BcKeyFingerprintCalculator())); + isTrue(!subkeyIdentifier.matches(privateKey, new BcKeyFingerprintCalculator())); + + KeyIdentifier wildcard = KeyIdentifier.wildcard(); + isTrue(wildcard.matches(primaryKey)); + isTrue(wildcard.matches(subkey)); + isTrue(wildcard.matches(privateKey, new BcKeyFingerprintCalculator())); + + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + primaryKey)); + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + primaryKey.getPublicKey())); + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + subkey)); + isTrue(KeyIdentifier.matches( + java.util.Arrays.asList(primaryIdentifier, subkeyIdentifier), + subkey.getPublicKey())); + } + + /** + * Return the v6 test key from RFC9580. + * Fingerprints: + *
    + *
  • CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9
  • + *
  • 12C83F1E706F6308FE151A417743A1F033790E93E9978488D1DB378DA9930885
  • + *
+ * @return test key + * @throws IOException + */ + private PGPSecretKeyRing getV6Key() + throws IOException + { + String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "\n" + + "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB\n" + + "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ\n" + + "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + + "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh\n" + + "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe\n" + + "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/\n" + + "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG\n" + + "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6\n" + + "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE\n" + + "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr\n" + + "k0mXubZvyl4GBg==\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + ByteArrayInputStream bIn = new ByteArrayInputStream(KEY.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + return (PGPSecretKeyRing) objFac.nextObject(); + } + + /** + * Return the 'Alice' test key. + * Fingerprints: + *
    + *
  • EB85BB5FA33A75E15E944E63F231550C4F47E38E
  • + *
  • EA02B24FFD4C1B96616D3DF24766F6B9D5F21EB6
  • + *
+ * @return Alice test key + * @throws IOException + */ + private PGPSecretKeyRing getV4Key() + throws IOException + { + String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: Alice's OpenPGP Transferable Secret Key\n" + + "\n" + + "lFgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RtCZBbGlj\n" + + "ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPoiQBBMWCAA4AhsDBQsJ\n" + + "CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l\n" + + "nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf\n" + + "a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICnF0EXEcE6RIKKwYB\n" + + "BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA\n" + + "/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK6IeAQYFggAIBYhBOuF\n" + + "u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM\n" + + "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb\n" + + "Pnn+We1aTBhaGa86AQ==\n" + + "=n8OM\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + ByteArrayInputStream bIn = new ByteArrayInputStream(KEY.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + return (PGPSecretKeyRing) objFac.nextObject(); + } + + public static void main(String[] args) + { + runTest(new KeyIdentifierTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/OpenPGPTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/OpenPGPTest.java index 7177a0cbd9..33a062c393 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/OpenPGPTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/OpenPGPTest.java @@ -937,6 +937,7 @@ public void testPGPLiteralData() PGPEncryptedDataList encList = (PGPEncryptedDataList)pgpF.nextObject(); PGPPublicKeyEncryptedData encP = (PGPPublicKeyEncryptedData)encList.get(0); + isTrue((encP.getKeyIdentifier().getKeyId())==encP.getVersion()); isEquals(encP.getAlgorithm(), 1); isEquals(encP.getVersion(), 3); PGPPrivateKey pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));