diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index 5f1992cb5..b76d8f987 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -56,6 +56,7 @@ import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.EmptyCommitException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffEntry;
@@ -651,6 +652,14 @@ public class CommitCommandTest extends RepositoryTestCase {
signingCommitters[0] = signingCommitter;
callCount.incrementAndGet();
}
+
+ @Override
+ public boolean canLocateSigningKey(String gpgSigningKey,
+ PersonIdent signingCommitter,
+ CredentialsProvider credentialsProvider)
+ throws CanceledException {
+ return false;
+ }
});
// first call should use config, which is expected to be null at
@@ -706,6 +715,14 @@ public class CommitCommandTest extends RepositoryTestCase {
PersonIdent signingCommitter, CredentialsProvider credentialsProvider) {
callCount.incrementAndGet();
}
+
+ @Override
+ public boolean canLocateSigningKey(String gpgSigningKey,
+ PersonIdent signingCommitter,
+ CredentialsProvider credentialsProvider)
+ throws CanceledException {
+ return false;
+ }
});
// first call should use config, which is expected to be null at
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java
index 7796c2058..99a23c6e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.lib;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.lib.internal.BouncyCastleGpgSigner;
import org.eclipse.jgit.transport.CredentialsProvider;
@@ -95,9 +96,11 @@ public abstract class GpgSigner {
* the commit to sign (must not be null
and must be
* complete to allow proper calculation of payload)
* @param gpgSigningKey
- * the signing key (passed as is to the GPG signing tool)
+ * the signing key to locate (passed as is to the GPG signing
+ * tool as is; eg., value of user.signingkey
)
* @param committer
- * the signing identity (to help with key lookup)
+ * the signing identity (to help with key lookup in case signing
+ * key is not specified)
* @param credentialsProvider
* provider to use when querying for signing key credentials (eg.
* passphrase)
@@ -106,7 +109,30 @@ public abstract class GpgSigner {
* passphrase)
*/
public abstract void sign(@NonNull CommitBuilder commit,
- String gpgSigningKey, @NonNull PersonIdent committer,
+ @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
+ CredentialsProvider credentialsProvider) throws CanceledException;
+
+ /**
+ * Indicates if a signing key is available for the specified committer
+ * and/or signing key.
+ *
+ * @param gpgSigningKey
+ * the signing key to locate (passed as is to the GPG signing
+ * tool as is; eg., value of user.signingkey
)
+ * @param committer
+ * the signing identity (to help with key lookup in case signing
+ * key is not specified)
+ * @param credentialsProvider
+ * provider to use when querying for signing key credentials (eg.
+ * passphrase)
+ * @return true
if a signing key is available,
+ * false
otherwise
+ * @throws CanceledException
+ * when signing was canceled (eg., user aborted when entering
+ * passphrase)
+ */
+ public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey,
+ @NonNull PersonIdent committer,
CredentialsProvider credentialsProvider) throws CanceledException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
index f447912f0..4d696dd9e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
@@ -59,8 +59,10 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.GpgSignature;
@@ -90,27 +92,50 @@ public class BouncyCastleGpgSigner extends GpgSigner {
}
@Override
- public void sign(@NonNull CommitBuilder commit, String gpgSigningKey,
- @NonNull PersonIdent committer,
- CredentialsProvider credentialsProvider) throws CanceledException {
+ public boolean canLocateSigningKey(@Nullable String gpgSigningKey,
+ PersonIdent committer, CredentialsProvider credentialsProvider)
+ throws CanceledException {
+ try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
+ credentialsProvider)) {
+ BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
+ committer, passphrasePrompt);
+ return gpgKey != null;
+ } catch (PGPException | IOException | URISyntaxException e) {
+ return false;
+ }
+ }
+
+ private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey,
+ PersonIdent committer,
+ BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt)
+ throws CanceledException, UnsupportedCredentialItem, IOException,
+ PGPException, URISyntaxException {
if (gpgSigningKey == null || gpgSigningKey.isEmpty()) {
gpgSigningKey = committer.getEmailAddress();
}
+ BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator(
+ gpgSigningKey, passphrasePrompt);
+
+ return keyHelper.findSecretKey();
+ }
+
+ @Override
+ public void sign(@NonNull CommitBuilder commit,
+ @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
+ CredentialsProvider credentialsProvider) throws CanceledException {
try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
credentialsProvider)) {
- BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator(
- gpgSigningKey, passphrasePrompt);
-
- BouncyCastleGpgKey gpgKey = keyHelper.findSecretKey();
+ BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
+ committer, passphrasePrompt);
PGPSecretKey secretKey = gpgKey.getSecretKey();
if (secretKey == null) {
throw new JGitInternalException(
JGitText.get().unableToSignCommitNoSecretKey);
}
- char[] passphrase = passphrasePrompt
- .getPassphrase(secretKey.getPublicKey().getFingerprint(),
- gpgKey.getOrigin());
+ char[] passphrase = passphrasePrompt.getPassphrase(
+ secretKey.getPublicKey().getFingerprint(),
+ gpgKey.getOrigin());
PGPPrivateKey privateKey = secretKey
.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)