PublicKeyStore: Add method to choose a valid signer
Due to supporting multiple keys with the same key ID, finding whether
there is a key in the store to produce a given signature is a bit of a
pain. Add a static helper method to PublicKeyStore to make this
easier, and use it from PushCertificateChecker.
Change-Id: I18524412d95b56245755109f1e182cd5d8a43f7a
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
index 721b101..5d3128a 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PublicKeyStore.java
@@ -23,7 +23,9 @@
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -74,6 +76,29 @@
/** Ref where GPG public keys are stored. */
public static final String REFS_GPG_KEYS = "refs/meta/gpg-keys";
+ /**
+ * Choose the public key that produced a signature.
+ * <p>
+ * @param keyRings candidate keys.
+ * @param sig signature object.
+ * @param data signed payload.
+ * @return the key chosen from {@code keyRings} that was able to verify the
+ * signature, or null if none was found.
+ * @throws PGPException if an error occurred verifying the signature.
+ */
+ public static PGPPublicKey getSigner(Iterable<PGPPublicKeyRing> keyRings,
+ PGPSignature sig, byte[] data) throws PGPException {
+ for (PGPPublicKeyRing kr : keyRings) {
+ PGPPublicKey k = kr.getPublicKey();
+ sig.init(new BcPGPContentVerifierBuilderProvider(), k);
+ sig.update(data);
+ if (sig.verify()) {
+ return k;
+ }
+ }
+ return null;
+ }
+
private final Repository repo;
private ObjectReader reader;
private RevCommit tip;
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java
index 687729e..4fc641d 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/PushCertificateChecker.java
@@ -21,12 +21,10 @@
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
-import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PushCertificate;
@@ -66,7 +64,7 @@
@SuppressWarnings("resource")
Repository repo = getRepository();
try (PublicKeyStore store = new PublicKeyStore(repo)) {
- checkSignature(sig, cert, store.get(sig.getKeyID()), problems);
+ checkSignature(sig, cert, store, problems);
checkCustom(repo, problems);
} finally {
if (shouldClose(repo)) {
@@ -129,47 +127,31 @@
return null;
}
- private void checkSignature(PGPSignature sig,
- PushCertificate cert, PGPPublicKeyRingCollection keys,
- List<String> problems) {
- List<String> deferredProblems = new ArrayList<>();
- boolean anyKeys = false;
- for (PGPPublicKeyRing kr : keys) {
- PGPPublicKey k = kr.getPublicKey();
- anyKeys = true;
- try {
- sig.init(new BcPGPContentVerifierBuilderProvider(), k);
- sig.update(Constants.encode(cert.toText()));
- if (!sig.verify()) {
- // TODO(dborowitz): Privacy issues with exposing fingerprint/user ID
- // of keys having the same ID as the pusher's key?
- deferredProblems.add(
- "Signature not valid with public key: " + keyToString(k));
- continue;
- }
- CheckResult result = publicKeyChecker.check(k);
- if (result.isOk()) {
- return;
- }
- StringBuilder err = new StringBuilder("Invalid public key ")
- .append(keyToString(k))
- .append(":");
- for (String problem : result.getProblems()) {
- err.append("\n ").append(problem);
- }
- problems.add(err.toString());
- return;
- } catch (PGPException e) {
- deferredProblems.add(
- "Error checking signature with public key " + keyToString(k)
- + ": " + e.getMessage());
- }
+ private void checkSignature(PGPSignature sig, PushCertificate cert,
+ PublicKeyStore store, List<String> problems)
+ throws PGPException, IOException {
+ PGPPublicKeyRingCollection keys = store.get(sig.getKeyID());
+ if (!keys.getKeyRings().hasNext()) {
+ problems.add("No public keys found for key ID "
+ + keyIdToString(sig.getKeyID()));
+ return;
}
- if (!anyKeys) {
- problems.add(
- "No public keys found for key ID " + keyIdToString(sig.getKeyID()));
- } else {
- problems.addAll(deferredProblems);
+ PGPPublicKey signer =
+ PublicKeyStore.getSigner(keys, sig, Constants.encode(cert.toText()));
+ if (signer == null) {
+ problems.add("Signature by " + keyIdToString(sig.getKeyID())
+ + " is not valid");
+ return;
+ }
+ CheckResult result = publicKeyChecker.check(signer);
+ if (!result.isOk()) {
+ StringBuilder err = new StringBuilder("Invalid public key ")
+ .append(keyToString(signer))
+ .append(":");
+ for (String problem : result.getProblems()) {
+ err.append("\n ").append(problem);
+ }
+ problems.add(err.toString());
}
}
}