diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/lib')
5 files changed, 22 insertions, 893 deletions
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 d953a01945..5b32cf0b5f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java @@ -9,11 +9,16 @@ */ package org.eclipse.jgit.lib; +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + 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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Creates GPG signatures for Git objects. @@ -21,8 +26,23 @@ import org.eclipse.jgit.transport.CredentialsProvider; * @since 5.3 */ public abstract class GpgSigner { + private static final Logger LOG = LoggerFactory.getLogger(GpgSigner.class); + + private static GpgSigner defaultSigner = loadGpgSigner(); - private static GpgSigner defaultSigner = new BouncyCastleGpgSigner(); + private static GpgSigner loadGpgSigner() { + try { + ServiceLoader<GpgSigner> loader = ServiceLoader + .load(GpgSigner.class); + Iterator<GpgSigner> iter = loader.iterator(); + if (iter.hasNext()) { + return iter.next(); + } + } catch (ServiceConfigurationError e) { + LOG.error(e.getMessage(), e); + } + return null; + } /** * Get the default signer, or <code>null</code>. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java deleted file mode 100644 index 8601d7c94f..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2018, Salesforce. and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib.internal; - -import java.nio.file.Path; - -import org.bouncycastle.openpgp.PGPSecretKey; - -/** - * Container which holds a {@link #getSecretKey()} together with the - * {@link #getOrigin() path it was loaded from}. - */ -class BouncyCastleGpgKey { - - private PGPSecretKey secretKey; - - private Path origin; - - public BouncyCastleGpgKey(PGPSecretKey secretKey, Path origin) { - this.secretKey = secretKey; - this.origin = origin; - } - - public PGPSecretKey getSecretKey() { - return secretKey; - } - - public Path getOrigin() { - return origin; - } -}
\ No newline at end of file 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 deleted file mode 100644 index 8a32299dd3..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java +++ /dev/null @@ -1,614 +0,0 @@ -/* - * Copyright (C) 2018, Salesforce. and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib.internal; - -import static java.nio.file.Files.exists; -import static java.nio.file.Files.newInputStream; - -import java.io.BufferedInputStream; -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; -import java.nio.file.Paths; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.text.MessageFormat; -import java.util.Iterator; -import java.util.Locale; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.bouncycastle.gpg.SExprParser; -import org.bouncycastle.gpg.keybox.BlobType; -import org.bouncycastle.gpg.keybox.KeyBlob; -import org.bouncycastle.gpg.keybox.KeyBox; -import org.bouncycastle.gpg.keybox.KeyInformation; -import org.bouncycastle.gpg.keybox.PublicKeyRingBlob; -import org.bouncycastle.gpg.keybox.UserID; -import org.bouncycastle.gpg.keybox.jcajce.JcaKeyBoxBuilder; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyFlags; -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; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory; -import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; -import org.bouncycastle.util.encoders.Hex; -import org.eclipse.jgit.annotations.NonNull; -import org.eclipse.jgit.api.errors.CanceledException; -import org.eclipse.jgit.errors.UnsupportedCredentialItem; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.util.FS; -import org.eclipse.jgit.util.StringUtils; -import org.eclipse.jgit.util.SystemReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Locates GPG keys from either <code>~/.gnupg/private-keys-v1.d</code> or - * <code>~/.gnupg/secring.gpg</code> - */ -class BouncyCastleGpgKeyLocator { - - /** Thrown if a keybox file exists but doesn't contain an OpenPGP key. */ - private static class NoOpenPgpKeyException extends Exception { - - private static final long serialVersionUID = 1L; - - } - - private static final Logger log = LoggerFactory - .getLogger(BouncyCastleGpgKeyLocator.class); - - private static final Path GPG_DIRECTORY = findGpgDirectory(); - - private static final Path USER_KEYBOX_PATH = GPG_DIRECTORY - .resolve("pubring.kbx"); //$NON-NLS-1$ - - 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$ - - private final String signingKey; - - private BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt; - - private static Path findGpgDirectory() { - SystemReader system = SystemReader.getInstance(); - if (system.isWindows()) { - // On Windows prefer %APPDATA%\gnupg if it exists, even if Cygwin is - // used. - String appData = system.getenv("APPDATA"); //$NON-NLS-1$ - if (appData != null && !appData.isEmpty()) { - try { - Path directory = Paths.get(appData).resolve("gnupg"); //$NON-NLS-1$ - if (Files.isDirectory(directory)) { - return directory; - } - } catch (SecurityException | InvalidPathException e) { - // Ignore and return the default location below. - } - } - } - // All systems, including Cygwin and even Windows if - // %APPDATA%\gnupg doesn't exist: ~/.gnupg - File home = FS.DETECTED.userHome(); - if (home == null) { - // Oops. What now? - home = new File(".").getAbsoluteFile(); //$NON-NLS-1$ - } - return home.toPath().resolve(".gnupg"); //$NON-NLS-1$ - } - - /** - * Create a new key locator for the specified signing key. - * <p> - * The signing key must either be a hex representation of a specific key or - * a user identity substring (eg., email address). All keys in the KeyBox - * will be looked up in the order as returned by the KeyBox. A key id will - * be searched before attempting to find a key by user id. - * </p> - * - * @param signingKey - * the signing key to search for - * @param passphrasePrompt - * the provider to use when asking for key passphrase - */ - public BouncyCastleGpgKeyLocator(String signingKey, - @NonNull BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt) { - this.signingKey = signingKey; - this.passphrasePrompt = passphrasePrompt; - } - - private PGPSecretKey attemptParseSecretKey(Path keyFile, - PGPDigestCalculatorProvider calculatorProvider, - PBEProtectionRemoverFactory passphraseProvider, - PGPPublicKey publicKey) { - try (InputStream in = newInputStream(keyFile)) { - return new SExprParser(calculatorProvider).parseSecretKey( - new BufferedInputStream(in), passphraseProvider, publicKey); - } catch (IOException | PGPException | ClassCastException e) { - if (log.isDebugEnabled()) - log.debug("Ignoring unreadable file '{}': {}", keyFile, //$NON-NLS-1$ - e.getMessage(), e); - return null; - } - } - - /** - * Checks whether a given OpenPGP {@code userId} matches a given - * {@code signingKeySpec}, which is supposed to have one of the formats - * defined by GPG. - * <p> - * Not all formats are supported; only formats starting with '=', '<', - * '@', and '*' are handled. Any other format results in a case-insensitive - * substring match. - * </p> - * - * @param userId - * of a key - * @param signingKeySpec - * GPG key identification - * @return whether the {@code userId} matches - * @see <a href= - * "https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html">GPG - * Documentation: How to Specify a User ID</a> - */ - static boolean containsSigningKey(String userId, String signingKeySpec) { - if (StringUtils.isEmptyOrNull(userId) - || StringUtils.isEmptyOrNull(signingKeySpec)) { - return false; - } - String toMatch = signingKeySpec; - if (toMatch.startsWith("0x") && toMatch.trim().length() > 2) { //$NON-NLS-1$ - return false; // Explicit fingerprint - } - int command = toMatch.charAt(0); - switch (command) { - case '=': - case '<': - case '@': - case '*': - toMatch = toMatch.substring(1); - if (toMatch.isEmpty()) { - return false; - } - break; - default: - break; - } - switch (command) { - case '=': - return userId.equals(toMatch); - case '<': { - int begin = userId.indexOf('<'); - int end = userId.indexOf('>', begin + 1); - int stop = toMatch.indexOf('>'); - return begin >= 0 && end > begin + 1 && stop > 0 - && userId.substring(begin + 1, end) - .equals(toMatch.substring(0, stop)); - } - case '@': { - int begin = userId.indexOf('<'); - int end = userId.indexOf('>', begin + 1); - return begin >= 0 && end > begin + 1 - && userId.substring(begin + 1, end).contains(toMatch); - } - default: - if (toMatch.trim().isEmpty()) { - return false; - } - return userId.toLowerCase(Locale.ROOT) - .contains(toMatch.toLowerCase(Locale.ROOT)); - } - } - - private String toFingerprint(String keyId) { - if (keyId.startsWith("0x")) { //$NON-NLS-1$ - return keyId.substring(2); - } - return keyId; - } - - private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob) - throws IOException { - String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT); - if (keyId.isEmpty()) { - return null; - } - for (KeyInformation keyInfo : keyBlob.getKeyInformation()) { - String fingerprint = Hex.toHexString(keyInfo.getFingerprint()) - .toLowerCase(Locale.ROOT); - if (fingerprint.endsWith(keyId)) { - return getPublicKey(keyBlob, keyInfo.getFingerprint()); - } - } - return null; - } - - private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob) - throws IOException { - for (UserID userID : keyBlob.getUserIds()) { - if (containsSigningKey(userID.getUserIDAsString(), signingKey)) { - return getSigningPublicKey(keyBlob); - } - } - return null; - } - - /** - * Finds a public key associated with the signing key. - * - * @param keyboxFile - * the KeyBox file - * @return publicKey the public key (maybe <code>null</code>) - * @throws IOException - * in case of problems reading the file - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException - * @throws NoOpenPgpKeyException - * if the file does not contain any OpenPGP key - */ - private PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile) - throws IOException, NoSuchAlgorithmException, - NoSuchProviderException, NoOpenPgpKeyException { - KeyBox keyBox = readKeyBoxFile(keyboxFile); - boolean hasOpenPgpKey = false; - for (KeyBlob keyBlob : keyBox.getKeyBlobs()) { - if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) { - hasOpenPgpKey = true; - PGPPublicKey key = findPublicKeyByKeyId(keyBlob); - if (key != null) { - return key; - } - key = findPublicKeyByUserId(keyBlob); - if (key != null) { - return key; - } - } - } - if (!hasOpenPgpKey) { - throw new NoOpenPgpKeyException(); - } - return null; - } - - /** - * 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 - * in case of issues reading key files - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException - * @throws PGPException - * 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; - 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)); - } - // 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); - } - } - - 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; - } - } - - private BouncyCastleGpgKey loadKeyFromSecring(Path secring) - throws IOException, PGPException { - PGPSecretKey secretKey = findSecretKeyInLegacySecring(signingKey, - secring); - - if (secretKey != null) { - if (!secretKey.isSigningKey()) { - throw new PGPException(MessageFormat - .format(JGitText.get().gpgNotASigningKey, signingKey)); - } - return new BouncyCastleGpgKey(secretKey, secring); - } - return null; - } - - private BouncyCastleGpgKey findSecretKeyForKeyBoxPublicKey( - PGPPublicKey publicKey, Path userKeyboxPath) - throws PGPException, CanceledException, UnsupportedCredentialItem, - URISyntaxException { - /* - * this is somewhat brute-force but there doesn't seem to be another - * way; we have to walk all private key files we find and try to open - * them - */ - - PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() - .build(); - - PBEProtectionRemoverFactory passphraseProvider = new JcePBEProtectionRemoverFactory( - passphrasePrompt.getPassphrase(publicKey.getFingerprint(), - userKeyboxPath)); - - try (Stream<Path> keyFiles = Files.walk(USER_SECRET_KEY_DIR)) { - for (Path keyFile : keyFiles.filter(Files::isRegularFile) - .collect(Collectors.toList())) { - PGPSecretKey secretKey = attemptParseSecretKey(keyFile, - calculatorProvider, passphraseProvider, publicKey); - if (secretKey != null) { - if (!secretKey.isSigningKey()) { - throw new PGPException(MessageFormat.format( - JGitText.get().gpgNotASigningKey, signingKey)); - } - return new BouncyCastleGpgKey(secretKey, userKeyboxPath); - } - } - - passphrasePrompt.clear(); - return null; - } catch (RuntimeException e) { - passphrasePrompt.clear(); - throw e; - } catch (IOException e) { - passphrasePrompt.clear(); - throw new PGPException(MessageFormat.format( - JGitText.get().gpgFailedToParseSecretKey, - USER_SECRET_KEY_DIR.toAbsolutePath()), e); - } - } - - /** - * Return the first suitable key for signing in the key ring collection. For - * this case we only expect there to be one key available for signing. - * </p> - * - * @param signingkey - * @param secringFile - * - * @return the first suitable PGP secret key found for signing - * @throws IOException - * on I/O related errors - * @throws PGPException - * on BouncyCastle errors - */ - private PGPSecretKey findSecretKeyInLegacySecring(String signingkey, - Path secringFile) throws IOException, PGPException { - - try (InputStream in = newInputStream(secringFile)) { - PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( - PGPUtil.getDecoderStream(new BufferedInputStream(in)), - new JcaKeyFingerprintCalculator()); - - String keyId = toFingerprint(signingkey).toLowerCase(Locale.ROOT); - Iterator<PGPSecretKeyRing> keyrings = pgpSec.getKeyRings(); - while (keyrings.hasNext()) { - PGPSecretKeyRing keyRing = keyrings.next(); - Iterator<PGPSecretKey> keys = keyRing.getSecretKeys(); - while (keys.hasNext()) { - PGPSecretKey key = keys.next(); - // try key id - String fingerprint = Hex - .toHexString(key.getPublicKey().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, signingKey)) { - return key; - } - } - } - } - } - 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 = toFingerprint(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, signingKey)) { - return key; - } - } - } - } - } - return null; - } - - private PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint) - throws IOException { - return ((PublicKeyRingBlob) blob).getPGPPublicKeyRing() - .getPublicKey(fingerprint); - } - - private PGPPublicKey getSigningPublicKey(KeyBlob blob) throws IOException { - PGPPublicKey masterKey = null; - Iterator<PGPPublicKey> keys = ((PublicKeyRingBlob) blob) - .getPGPPublicKeyRing().getPublicKeys(); - while (keys.hasNext()) { - PGPPublicKey key = keys.next(); - // only consider keys that have the [S] usage flag set - if (isSigningKey(key)) { - if (key.isMasterKey()) { - masterKey = key; - } else { - return key; - } - } - } - // return the master key if no other signing key was found or null if - // the master key did not have the signing flag set - return masterKey; - } - - private boolean isSigningKey(PGPPublicKey key) { - Iterator signatures = key.getSignatures(); - while (signatures.hasNext()) { - PGPSignature sig = (PGPSignature) signatures.next(); - if ((sig.getHashedSubPackets().getKeyFlags() - & PGPKeyFlags.CAN_SIGN) > 0) { - return true; - } - } - return false; - } - - private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException, - NoSuchAlgorithmException, NoSuchProviderException, - NoOpenPgpKeyException { - if (keyboxFile.toFile().length() == 0) { - throw new NoOpenPgpKeyException(); - } - KeyBox keyBox; - try (InputStream in = new BufferedInputStream( - newInputStream(keyboxFile))) { - keyBox = new JcaKeyBoxBuilder().build(in); - } - return keyBox; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java deleted file mode 100644 index 6e29af51d8..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java +++ /dev/null @@ -1,101 +0,0 @@ -/*- - * Copyright (C) 2019, Salesforce. and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib.internal; - -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.text.MessageFormat; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.util.encoders.Hex; -import org.eclipse.jgit.api.errors.CanceledException; -import org.eclipse.jgit.errors.UnsupportedCredentialItem; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.transport.CredentialItem.CharArrayType; -import org.eclipse.jgit.transport.CredentialItem.InformationalMessage; -import org.eclipse.jgit.transport.CredentialsProvider; -import org.eclipse.jgit.transport.URIish; - -/** - * Prompts for a passphrase and caches it until {@link #clear() cleared}. - * <p> - * Implements {@link AutoCloseable} so it can be used within a - * try-with-resources block. - * </p> - */ -class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable { - - private CharArrayType passphrase; - - private CredentialsProvider credentialsProvider; - - public BouncyCastleGpgKeyPassphrasePrompt( - CredentialsProvider credentialsProvider) { - this.credentialsProvider = credentialsProvider; - } - - /** - * Clears any cached passphrase - */ - public void clear() { - if (passphrase != null) { - passphrase.clear(); - passphrase = null; - } - } - - @Override - public void close() { - clear(); - } - - private URIish createURI(Path keyLocation) throws URISyntaxException { - return new URIish(keyLocation.toUri().toString()); - } - - /** - * Prompts use for a passphrase unless one was cached from a previous - * prompt. - * - * @param keyFingerprint - * the fingerprint to show to the user during prompting - * @param keyLocation - * the location the key was loaded from - * @return the passphrase (maybe <code>null</code>) - * @throws PGPException - * @throws CanceledException - * in case passphrase was not entered by user - * @throws URISyntaxException - * @throws UnsupportedCredentialItem - */ - public char[] getPassphrase(byte[] keyFingerprint, Path keyLocation) - throws PGPException, CanceledException, UnsupportedCredentialItem, - URISyntaxException { - if (passphrase == null) { - passphrase = new CharArrayType(JGitText.get().credentialPassphrase, - true); - } - - if (credentialsProvider == null) { - throw new PGPException(JGitText.get().gpgNoCredentialsProvider); - } - - if (passphrase.getValue() == null - && !credentialsProvider.get(createURI(keyLocation), - new InformationalMessage( - MessageFormat.format(JGitText.get().gpgKeyInfo, - Hex.toHexString(keyFingerprint))), - passphrase)) { - throw new CanceledException(JGitText.get().gpgSigningCancelled); - } - return passphrase.getValue(); - } - -} 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 deleted file mode 100644 index dfcfdab115..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2018, Salesforce. and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib.internal; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URISyntaxException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; - -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.BCPGOutputStream; -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -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; -import org.eclipse.jgit.lib.GpgSigner; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.transport.CredentialsProvider; - -/** - * GPG Signer using BouncyCastle library - */ -public class BouncyCastleGpgSigner extends GpgSigner { - - private static void registerBouncyCastleProviderIfNecessary() { - if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { - Security.addProvider(new BouncyCastleProvider()); - } - } - - /** - * Create a new instance. - * <p> - * The BounceCastleProvider will be registered if necessary. - * </p> - */ - public BouncyCastleGpgSigner() { - registerBouncyCastleProviderIfNecessary(); - } - - @Override - 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 | NoSuchAlgorithmException - | NoSuchProviderException | URISyntaxException e) { - return false; - } - } - - private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey, - PersonIdent committer, - BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt) - throws CanceledException, UnsupportedCredentialItem, IOException, - NoSuchAlgorithmException, NoSuchProviderException, 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)) { - 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()); - PGPPrivateKey privateKey = secretKey - .extractPrivateKey(new JcePBESecretKeyDecryptorBuilder() - .setProvider(BouncyCastleProvider.PROVIDER_NAME) - .build(passphrase)); - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( - new JcaPGPContentSignerBuilder( - secretKey.getPublicKey().getAlgorithm(), - HashAlgorithmTags.SHA256).setProvider( - BouncyCastleProvider.PROVIDER_NAME)); - signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); - PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(); - subpacketGenerator.setIssuerFingerprint(false, - secretKey.getPublicKey()); - signatureGenerator - .setHashedSubpackets(subpacketGenerator.generate()); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - try (BCPGOutputStream out = new BCPGOutputStream( - new ArmoredOutputStream(buffer))) { - signatureGenerator.update(commit.build()); - signatureGenerator.generate().encode(out); - } - commit.setGpgSignature(new GpgSignature(buffer.toByteArray())); - } catch (PGPException | IOException | NoSuchAlgorithmException - | NoSuchProviderException | URISyntaxException e) { - throw new JGitInternalException(e.getMessage(), e); - } - } -} |