aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java152
1 files changed, 126 insertions, 26 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
index e3e9d41de3..11db7c5998 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
@@ -50,6 +50,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
+import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
@@ -72,6 +73,8 @@ import org.bouncycastle.gpg.keybox.UserID;
import org.bouncycastle.gpg.keybox.jcajce.JcaKeyBoxBuilder;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
@@ -115,6 +118,9 @@ class BouncyCastleGpgKeyLocator {
private static final Path USER_SECRET_KEY_DIR = GPG_DIRECTORY
.resolve("private-keys-v1.d"); //$NON-NLS-1$
+ private static final Path USER_PGP_PUBRING_FILE = GPG_DIRECTORY
+ .resolve("pubring.gpg"); //$NON-NLS-1$
+
private static final Path USER_PGP_LEGACY_SECRING_FILE = GPG_DIRECTORY
.resolve("secring.gpg"); //$NON-NLS-1$
@@ -250,8 +256,13 @@ class BouncyCastleGpgKeyLocator {
}
/**
- * Use pubring.kbx when available, if not fallback to secring.gpg or secret
- * key path provided to parse and return secret key
+ * If there is a private key directory containing keys, use pubring.kbx or
+ * pubring.gpg to find the public key; then try to find the secret key in
+ * the directory.
+ * <p>
+ * If there is no private key directory (or it doesn't contain any keys),
+ * try to find the key in secring.gpg directly.
+ * </p>
*
* @return the secret key
* @throws IOException
@@ -259,51 +270,97 @@ class BouncyCastleGpgKeyLocator {
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws PGPException
- * in case of issues finding a key
+ * in case of issues finding a key, including no key found
* @throws CanceledException
* @throws URISyntaxException
* @throws UnsupportedCredentialItem
*/
+ @NonNull
public BouncyCastleGpgKey findSecretKey() throws IOException,
NoSuchAlgorithmException, NoSuchProviderException, PGPException,
CanceledException, UnsupportedCredentialItem, URISyntaxException {
BouncyCastleGpgKey key;
- if (exists(USER_KEYBOX_PATH)) {
- try {
- key = loadKeyFromKeybox(USER_KEYBOX_PATH);
- if (key != null) {
- return key;
+ PGPPublicKey publicKey = null;
+ if (hasKeyFiles(USER_SECRET_KEY_DIR)) {
+ // Use pubring.kbx or pubring.gpg to find the public key, then try
+ // the key files in the directory. If the public key was found in
+ // pubring.gpg also try secring.gpg to find the secret key.
+ if (exists(USER_KEYBOX_PATH)) {
+ try {
+ publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH);
+ if (publicKey != null) {
+ key = findSecretKeyForKeyBoxPublicKey(publicKey,
+ USER_KEYBOX_PATH);
+ if (key != null) {
+ return key;
+ }
+ throw new PGPException(MessageFormat.format(
+ JGitText.get().gpgNoSecretKeyForPublicKey,
+ Long.toHexString(publicKey.getKeyID())));
+ }
+ throw new PGPException(MessageFormat.format(
+ JGitText.get().gpgNoPublicKeyFound, signingKey));
+ } catch (NoOpenPgpKeyException e) {
+ // There are no OpenPGP keys in the keybox at all: try the
+ // pubring.gpg, if it exists.
+ if (log.isDebugEnabled()) {
+ log.debug("{} does not contain any OpenPGP keys", //$NON-NLS-1$
+ USER_KEYBOX_PATH);
+ }
+ }
+ }
+ if (exists(USER_PGP_PUBRING_FILE)) {
+ publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE);
+ if (publicKey != null) {
+ // GPG < 2.1 may have both; the agent using the directory
+ // and gpg using secring.gpg. GPG >= 2.1 delegates all
+ // secret key handling to the agent and doesn't use
+ // secring.gpg at all, even if it exists. Which means for us
+ // we have to try both since we don't know which GPG version
+ // the user has.
+ key = findSecretKeyForKeyBoxPublicKey(publicKey,
+ USER_PGP_PUBRING_FILE);
+ if (key != null) {
+ return key;
+ }
}
+ }
+ if (publicKey == null) {
throw new PGPException(MessageFormat.format(
JGitText.get().gpgNoPublicKeyFound, signingKey));
- } catch (NoOpenPgpKeyException e) {
- // Ignore and try the secring.gpg, if it exists.
- if (log.isDebugEnabled()) {
- log.debug("{} does not contain any OpenPGP keys", //$NON-NLS-1$
- USER_KEYBOX_PATH);
- }
}
+ // We found a public key, but didn't find the secret key in the
+ // private key directory. Go try the secring.gpg.
}
+ boolean hasSecring = false;
if (exists(USER_PGP_LEGACY_SECRING_FILE)) {
+ hasSecring = true;
key = loadKeyFromSecring(USER_PGP_LEGACY_SECRING_FILE);
if (key != null) {
return key;
}
+ }
+ if (publicKey != null) {
+ throw new PGPException(MessageFormat.format(
+ JGitText.get().gpgNoSecretKeyForPublicKey,
+ Long.toHexString(publicKey.getKeyID())));
+ } else if (hasSecring) {
+ // publicKey == null: user has _only_ pubring.gpg/secring.gpg.
throw new PGPException(MessageFormat.format(
JGitText.get().gpgNoKeyInLegacySecring, signingKey));
+ } else {
+ throw new PGPException(JGitText.get().gpgNoKeyring);
}
- throw new PGPException(JGitText.get().gpgNoKeyring);
}
- private BouncyCastleGpgKey loadKeyFromKeybox(Path keybox)
- throws NoOpenPgpKeyException, NoSuchAlgorithmException,
- NoSuchProviderException, IOException, CanceledException,
- UnsupportedCredentialItem, PGPException, URISyntaxException {
- PGPPublicKey publicKey = findPublicKeyInKeyBox(keybox);
- if (publicKey != null) {
- return findSecretKeyForKeyBoxPublicKey(publicKey, keybox);
+ private boolean hasKeyFiles(Path dir) {
+ try (DirectoryStream<Path> contents = Files.newDirectoryStream(dir,
+ "*.key")) { //$NON-NLS-1$
+ return contents.iterator().hasNext();
+ } catch (IOException e) {
+ // Not a directory, or something else
+ return false;
}
- return null;
}
private BouncyCastleGpgKey loadKeyFromSecring(Path secring)
@@ -353,9 +410,7 @@ class BouncyCastleGpgKeyLocator {
}
passphrasePrompt.clear();
- throw new PGPException(MessageFormat.format(
- JGitText.get().gpgNoSecretKeyForPublicKey,
- Long.toHexString(publicKey.getKeyID())));
+ return null;
} catch (RuntimeException e) {
passphrasePrompt.clear();
throw e;
@@ -417,6 +472,51 @@ class BouncyCastleGpgKeyLocator {
return null;
}
+ /**
+ * Return the first public key matching the key id ({@link #signingKey}.
+ *
+ * @param pubringFile
+ *
+ * @return the PGP public key, or {@code null} if none found
+ * @throws IOException
+ * on I/O related errors
+ * @throws PGPException
+ * on BouncyCastle errors
+ */
+ private PGPPublicKey findPublicKeyInPubring(Path pubringFile)
+ throws IOException, PGPException {
+ try (InputStream in = newInputStream(pubringFile)) {
+ PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
+ new BufferedInputStream(in),
+ new JcaKeyFingerprintCalculator());
+
+ String keyId = signingKey.toLowerCase(Locale.ROOT);
+ Iterator<PGPPublicKeyRing> keyrings = pgpPub.getKeyRings();
+ while (keyrings.hasNext()) {
+ PGPPublicKeyRing keyRing = keyrings.next();
+ Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
+ while (keys.hasNext()) {
+ PGPPublicKey key = keys.next();
+ // try key id
+ String fingerprint = Hex.toHexString(key.getFingerprint())
+ .toLowerCase(Locale.ROOT);
+ if (fingerprint.endsWith(keyId)) {
+ return key;
+ }
+ // try user id
+ Iterator<String> userIDs = key.getUserIDs();
+ while (userIDs.hasNext()) {
+ String userId = userIDs.next();
+ if (containsSigningKey(userId)) {
+ return key;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
private PGPPublicKey getFirstPublicKey(KeyBlob keyBlob) throws IOException {
return ((PublicKeyRingBlob) keyBlob).getPGPPublicKeyRing()
.getPublicKey();