/* * Copyright (C) 2018, 2024 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.gpg.bc.internal; import org.eclipse.jgit.nls.NLS; import org.eclipse.jgit.nls.TranslationBundle; /** * Externalized text messages for localization. */ @SuppressWarnings("MissingSummary") public final class BCText extends TranslationBundle { /** * Get an instance of this translation bundle. * * @return an instance of this translation bundle */ public static BCText get() { return NLS.getBundleFor(BCText.class); } // @formatter:off /***/ public String corrupt25519Key; /***/ public String credentialPassphrase; /***/ public String gpgFailedToParseSecretKey; /***/ public String gpgNoCredentialsProvider; /***/ public String gpgNoKeygrip; /***/ public String gpgNoKeyring; /***/ public String gpgNoKeyInLegacySecring; /***/ public String gpgNoPublicKeyFound; /***/ public String gpgNoSecretKeyForPublicKey; /***/ public String gpgNotASigningKey; /***/ public String gpgKeyInfo; /***/ public String gpgSigningCancelled; /***/ public String keyAlgorithmMismatch; /***/ public String keyMismatch; /***/ public String logWarnGnuPGHome; /***/ public String logWarnGpgHomeProperty; /***/ public String nonSignatureError; /***/ public String signatureInconsistent; /***/ public String signatureKeyLookupError; /***/ public String signatureNoKeyInfo; /***/ public String signatureNoPublicKey; /***/ public String signatureNoSigningKey; /***/ public String signatureParseError; /***/ public String signatureVerificationError; /***/ public String unableToSignCommitNoSecretKey; /***/ public String uncompressed25519Key; /***/ public String unknownCurve; /***/ public String unknownCurveParameters; /***/ public String unknownKeyType; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 867 Content-Disposition: inline; filename="BouncyCastleGpgKey.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "bc26cb98a3276b6a349862b28b80eba868c4fc8a" /* * 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.gpg.bc.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; } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 24964 Content-Disposition: inline; filename="BouncyCastleGpgKeyLocator.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "970e7df3c90e9e6c5bebb5a3b901542105e0bc4c" /* * Copyright (C) 2018, 2021 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.gpg.bc.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.FileNotFoundException; 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.NoSuchFileException; 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.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; 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.PGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; 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.gpg.bc.internal.keys.KeyGrip; import org.eclipse.jgit.gpg.bc.internal.keys.SecretKeys; 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 ~/.gnupg/private-keys-v1.d or * ~/.gnupg/secring.gpg */ public 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); 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(); Function resolveTilde = s -> { if (s.startsWith("~/") || s.startsWith("~" + File.separatorChar)) { //$NON-NLS-1$ //$NON-NLS-2$ return new File(FS.DETECTED.userHome(), s.substring(2)) .getAbsoluteFile().toPath(); } return Paths.get(s); }; Path path = checkDirectory(system.getProperty("jgit.gpg.home"), //$NON-NLS-1$ resolveTilde, s -> log.warn(BCText.get().logWarnGpgHomeProperty, s)); if (path != null) { return path; } path = checkDirectory(system.getenv("GNUPGHOME"), resolveTilde, //$NON-NLS-1$ s -> log.warn(BCText.get().logWarnGnuPGHome, s)); if (path != null) { return path; } if (system.isWindows()) { // On Windows prefer %APPDATA%\gnupg if it exists, even if Cygwin is // used. path = checkDirectory(system.getenv("APPDATA"), //$NON-NLS-1$ s -> Paths.get(s).resolve("gnupg"), null); //$NON-NLS-1$ if (path != null) { return path; } } // All systems, including Cygwin and even Windows if // %APPDATA%\gnupg doesn't exist: ~/.gnupg return resolveTilde.apply("~/.gnupg"); //$NON-NLS-1$ } private static Path checkDirectory(String dir, Function toPath, Consumer warn) { if (!StringUtils.isEmptyOrNull(dir)) { try { Path directory = toPath.apply(dir); if (Files.isDirectory(directory)) { return directory; } } catch (SecurityException | InvalidPathException e) { // Ignore, warn, and try other known directories } if (warn != null) { warn.accept(dir); } } return null; } /** * Create a new key locator for the specified signing key. *

* 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. *

* * @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, SecretKeys.PassphraseSupplier passphraseSupplier, PGPPublicKey publicKey) throws IOException, PGPException, CanceledException, UnsupportedCredentialItem, URISyntaxException { try (InputStream in = newInputStream(keyFile)) { return SecretKeys.readSecretKey(in, calculatorProvider, passphraseSupplier, publicKey); } } /** * Checks whether a given OpenPGP {@code userId} matches a given * {@code signingKeySpec}, which is supposed to have one of the formats * defined by GPG. *

* Not all formats are supported; only formats starting with '=', '<', * '@', and '*' are handled. Any other format results in a case-insensitive * substring match. *

* * @param userId * of a key * @param signingKeySpec * GPG key identification * @return whether the {@code userId} matches * @see GPG * Documentation: How to Specify a User ID */ 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) .equalsIgnoreCase(toMatch.substring(0, stop)); } case '@': { int begin = userId.indexOf('<'); int end = userId.indexOf('>', begin + 1); return begin >= 0 && end > begin + 1 && containsIgnoreCase(userId.substring(begin + 1, end), toMatch); } default: if (toMatch.trim().isEmpty()) { return false; } return containsIgnoreCase(userId, toMatch); } } private static boolean containsIgnoreCase(String a, String b) { int alength = a.length(); int blength = b.length(); for (int i = 0; i + blength <= alength; i++) { if (a.regionMatches(true, i, b, 0, blength)) { return true; } } return false; } private static String toFingerprint(String keyId) { if (keyId.startsWith("0x")) { //$NON-NLS-1$ return keyId.substring(2); } return keyId; } static BouncyCastleGpgPublicKey findPublicKey(String fingerprint, String keySpec) throws IOException, PGPException { BouncyCastleGpgPublicKey result = findPublicKeyInPubring( USER_PGP_PUBRING_FILE, fingerprint, keySpec); if (result == null && exists(USER_KEYBOX_PATH)) { try { result = findPublicKeyInKeyBox(USER_KEYBOX_PATH, fingerprint, keySpec); } catch (NoSuchAlgorithmException | NoSuchProviderException | IOException | NoOpenPgpKeyException e) { log.error(e.getMessage(), e); } } return result; } private static PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob, String keyId) throws IOException { 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 static PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob, String keySpec) throws IOException { for (UserID userID : keyBlob.getUserIds()) { if (containsSigningKey(userID.getUserIDAsString(), keySpec)) { return getSigningPublicKey(keyBlob); } } return null; } /** * Finds a public key associated with the signing key. * * @param keyboxFile * the KeyBox file * @param keyId * to look for, may be null * @param keySpec * to look for * @return publicKey the public key (maybe null) * @throws IOException * in case of problems reading the file * @throws NoSuchAlgorithmException * if an algorithm isn't available * @throws NoSuchProviderException * if a provider isn't available * @throws NoOpenPgpKeyException * if the file does not contain any OpenPGP key */ private static BouncyCastleGpgPublicKey findPublicKeyInKeyBox( Path keyboxFile, String keyId, String keySpec) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, NoOpenPgpKeyException { KeyBox keyBox = readKeyBoxFile(keyboxFile); String id = keyId != null ? keyId : toFingerprint(keySpec).toLowerCase(Locale.ROOT); boolean hasOpenPgpKey = false; for (KeyBlob keyBlob : keyBox.getKeyBlobs()) { if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) { hasOpenPgpKey = true; PGPPublicKey key = findPublicKeyByKeyId(keyBlob, id); if (key != null) { if (!isSigningKey(key)) { return null; } return new BouncyCastleGpgPublicKey(key, true, toStrings(keyBlob.getUserIds())); } key = findPublicKeyByUserId(keyBlob, keySpec); if (key != null) { return new BouncyCastleGpgPublicKey(key, true, toStrings(keyBlob.getUserIds())); } } } if (!hasOpenPgpKey) { throw new NoOpenPgpKeyException(); } return null; } private static List toStrings(List userIds) { if (userIds == null) { return Collections.emptyList(); } return userIds.stream().map(UserID::getUserIDAsString) .collect(Collectors.toList()); } /** * 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. *

* If there is no private key directory (or it doesn't contain any keys), * try to find the key in secring.gpg directly. *

* * @return the secret key * @throws IOException * in case of issues reading key files * @throws NoSuchAlgorithmException * algorithm is not available * @throws NoSuchProviderException * provider is not available * @throws PGPException * in case of issues finding a key, including no key found * @throws CanceledException * operation was cancelled * @throws URISyntaxException * URI is invalid * @throws UnsupportedCredentialItem * credential item is not supported */ @NonNull public BouncyCastleGpgKey findSecretKey() throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, CanceledException, UnsupportedCredentialItem, URISyntaxException { BouncyCastleGpgKey key; BouncyCastleGpgPublicKey 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, null, signingKey); if (publicKey != null) { key = findSecretKeyForKeyBoxPublicKey( publicKey.getPublicKey(), USER_KEYBOX_PATH); if (key != null) { return key; } throw new PGPException(MessageFormat.format( BCText.get().gpgNoSecretKeyForPublicKey, Long.toHexString( publicKey.getPublicKey().getKeyID()))); } throw new PGPException(MessageFormat.format( BCText.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, null, signingKey); 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.getPublicKey(), USER_PGP_PUBRING_FILE); if (key != null) { return key; } } } if (publicKey == null) { throw new PGPException(MessageFormat.format( BCText.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( BCText.get().gpgNoSecretKeyForPublicKey, Long.toHexString(publicKey.getPublicKey().getKeyID()))); } else if (hasSecring) { // publicKey == null: user has _only_ pubring.gpg/secring.gpg. throw new PGPException(MessageFormat.format( BCText.get().gpgNoKeyInLegacySecring, signingKey)); } else { throw new PGPException(BCText.get().gpgNoKeyring); } } private boolean hasKeyFiles(Path dir) { try (DirectoryStream 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(BCText.get().gpgNotASigningKey, signingKey)); } return new BouncyCastleGpgKey(secretKey, secring); } return null; } private BouncyCastleGpgKey findSecretKeyForKeyBoxPublicKey( PGPPublicKey publicKey, Path userKeyboxPath) throws PGPException, CanceledException, UnsupportedCredentialItem, URISyntaxException { byte[] keyGrip = null; try { keyGrip = KeyGrip.getKeyGrip(publicKey); } catch (PGPException e) { throw new PGPException( MessageFormat.format(BCText.get().gpgNoKeygrip, Hex.toHexString(publicKey.getFingerprint())), e); } String filename = Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT) + ".key"; //$NON-NLS-1$ Path keyFile = USER_SECRET_KEY_DIR.resolve(filename); if (!Files.exists(keyFile)) { return null; } boolean clearPrompt = false; try { PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() .build(); clearPrompt = true; PGPSecretKey secretKey = null; try { secretKey = attemptParseSecretKey(keyFile, calculatorProvider, () -> passphrasePrompt.getPassphrase( publicKey.getFingerprint(), userKeyboxPath), publicKey); } catch (PGPException e) { throw new PGPException(MessageFormat.format( BCText.get().gpgFailedToParseSecretKey, keyFile.toAbsolutePath()), e); } if (secretKey != null) { if (!secretKey.isSigningKey()) { throw new PGPException(MessageFormat.format( BCText.get().gpgNotASigningKey, signingKey)); } clearPrompt = false; return new BouncyCastleGpgKey(secretKey, userKeyboxPath); } return null; } catch (RuntimeException e) { throw e; } catch (FileNotFoundException | NoSuchFileException e) { clearPrompt = false; return null; } catch (IOException e) { throw new PGPException(MessageFormat.format( BCText.get().gpgFailedToParseSecretKey, keyFile.toAbsolutePath()), e); } finally { if (clearPrompt) { passphrasePrompt.clear(); } } } /** * 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. * * @param signingKeyName * the signing key * @param secringFile * the secring file * * @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 signingKeyName, Path secringFile) throws IOException, PGPException { try (InputStream in = newInputStream(secringFile)) { PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( PGPUtil.getDecoderStream(new BufferedInputStream(in)), new JcaKeyFingerprintCalculator()); String keyId = toFingerprint(signingKeyName).toLowerCase(Locale.ROOT); Iterator keyrings = pgpSec.getKeyRings(); while (keyrings.hasNext()) { PGPSecretKeyRing keyRing = keyrings.next(); Iterator 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 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 * to search * @param keyId * to look for, may be null * @param keySpec * to look for * * @return the PGP public key, or {@code null} if none found * @throws IOException * on I/O related errors * @throws PGPException * on BouncyCastle errors */ private static BouncyCastleGpgPublicKey findPublicKeyInPubring( Path pubringFile, String keyId, String keySpec) throws IOException, PGPException { try (InputStream in = newInputStream(pubringFile)) { PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( new BufferedInputStream(in), new JcaKeyFingerprintCalculator()); String id = keyId != null ? keyId : toFingerprint(keySpec).toLowerCase(Locale.ROOT); Iterator keyrings = pgpPub.getKeyRings(); BouncyCastleGpgPublicKey candidate = null; while (keyrings.hasNext()) { PGPPublicKeyRing keyRing = keyrings.next(); BouncyCastleGpgPublicKey newCandidate = findPublicKeyInPubring( keyRing, id, keySpec); if (newCandidate != null) { if (newCandidate.isExactMatch()) { return newCandidate; } else if (candidate == null) { candidate = newCandidate; } } } return candidate; } catch (FileNotFoundException | NoSuchFileException e) { return null; } } private static BouncyCastleGpgPublicKey findPublicKeyInPubring( PGPPublicKeyRing keyRing, String keyId, String keySpec) { Iterator keys = keyRing.getPublicKeys(); if (!keys.hasNext()) { return null; } PGPPublicKey masterKey = keys.next(); String fingerprint = Hex.toHexString(masterKey.getFingerprint()) .toLowerCase(Locale.ROOT); boolean masterFingerprintMatch = false; boolean userIdMatch = false; List userIds = new ArrayList<>(); masterKey.getUserIDs().forEachRemaining(userIds::add); if (fingerprint.endsWith(keyId)) { masterFingerprintMatch = true; } else { // Check the user IDs for (String userId : userIds) { if (containsSigningKey(userId, keySpec)) { userIdMatch = true; break; } } } if (masterFingerprintMatch) { if (isSigningKey(masterKey)) { return new BouncyCastleGpgPublicKey(masterKey, true, userIds); } } // Check subkeys -- they have no user ids, so only check for a // fingerprint match (unless the master key matched). PGPPublicKey candidate = null; while (keys.hasNext()) { PGPPublicKey subKey = keys.next(); if (!isSigningKey(subKey)) { continue; } if (masterFingerprintMatch) { candidate = subKey; break; } fingerprint = Hex.toHexString(subKey.getFingerprint()) .toLowerCase(Locale.ROOT); if (fingerprint.endsWith(keyId)) { return new BouncyCastleGpgPublicKey(subKey, true, userIds); } if (candidate == null) { candidate = subKey; } } if (candidate != null && (masterFingerprintMatch || userIdMatch)) { return new BouncyCastleGpgPublicKey(candidate, false, userIds); } return null; } private static PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint) throws IOException { return ((PublicKeyRingBlob) blob).getPGPPublicKeyRing() .getPublicKey(fingerprint); } private static PGPPublicKey getSigningPublicKey(KeyBlob blob) throws IOException { PGPPublicKey masterKey = null; Iterator 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 static 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 static 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; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3312 Content-Disposition: inline; filename="BouncyCastleGpgKeyPassphrasePrompt.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "463b661127aabd3184813c5e90b59853dd01adfb" /*- * Copyright (C) 2019, 2020 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.gpg.bc.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.transport.CredentialItem.InformationalMessage; import org.eclipse.jgit.transport.CredentialItem.Password; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.URIish; /** * Prompts for a passphrase and caches it until {@link #clear() cleared}. *

* Implements {@link AutoCloseable} so it can be used within a * try-with-resources block. *

*/ class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable { private Password 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 null) * @throws PGPException * if a PGP problem occurred * @throws CanceledException * in case passphrase was not entered by user * @throws URISyntaxException * if the URI isn't parseable * @throws UnsupportedCredentialItem * if a credential item isn't supported */ public char[] getPassphrase(byte[] keyFingerprint, Path keyLocation) throws PGPException, CanceledException, UnsupportedCredentialItem, URISyntaxException { if (passphrase == null) { passphrase = new Password(BCText.get().credentialPassphrase); } if (credentialsProvider == null) { throw new PGPException(BCText.get().gpgNoCredentialsProvider); } if (passphrase.getValue() == null && !credentialsProvider.get(createURI(keyLocation), new InformationalMessage( MessageFormat.format(BCText.get().gpgKeyInfo, Hex.toHexString(keyFingerprint))), passphrase)) { throw new CanceledException(BCText.get().gpgSigningCancelled); } return passphrase.getValue(); } /** * Determines whether a passphrase was already obtained. * * @return {@code true} if a passphrase is already set, {@code false} * otherwise */ public boolean hasPassphrase() { return passphrase != null && passphrase.getValue() != null; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 973 Content-Disposition: inline; filename="BouncyCastleGpgPublicKey.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "9ec5b455304277a110f12d23f70e6d93dc80a73c" /* * Copyright (C) 2024 Thomas Wolf 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.gpg.bc.internal; import java.util.List; import org.bouncycastle.openpgp.PGPPublicKey; /** * Container for GPG public keys. */ class BouncyCastleGpgPublicKey { private final PGPPublicKey publicKey; private final boolean exactMatch; private final List userIds; BouncyCastleGpgPublicKey(PGPPublicKey publicKey, boolean exactMatch, List userIds) { this.publicKey = publicKey; this.exactMatch = exactMatch; this.userIds = userIds; } PGPPublicKey getPublicKey() { return publicKey; } boolean isExactMatch() { return exactMatch; } List getUserIds() { return userIds; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8639 Content-Disposition: inline; filename="BouncyCastleGpgSignatureVerifier.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "5a3d43ba5434ea4a76d9ad3074fd81d050179d38" /* * Copyright (C) 2021, Thomas Wolf 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.gpg.bc.internal; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.time.Instant; import java.util.Date; import java.util.List; import java.util.Locale; import org.bouncycastle.bcpg.sig.IssuerFingerprint; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.bouncycastle.util.encoders.Hex; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SignatureVerifier; import org.eclipse.jgit.util.LRUMap; import org.eclipse.jgit.util.StringUtils; /** * A {@link SignatureVerifier} to verify GPG signatures using BouncyCastle. */ public class BouncyCastleGpgSignatureVerifier implements SignatureVerifier { private static final String NAME = "bc"; //$NON-NLS-1$ // To support more efficient signature verification of multiple objects we // cache public keys once found in a LRU cache. private static final Object NO_KEY = new Object(); private LRUMap byFingerprint = new LRUMap<>(16, 200); private LRUMap bySigner = new LRUMap<>(16, 200); @Override public String getName() { return NAME; } static PGPSignature parseSignature(InputStream in) throws IOException, PGPException { try (InputStream sigIn = PGPUtil.getDecoderStream(in)) { JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(sigIn); Object obj = pgpFactory.nextObject(); if (obj instanceof PGPCompressedData) { obj = new JcaPGPObjectFactory( ((PGPCompressedData) obj).getDataStream()).nextObject(); } if (obj instanceof PGPSignatureList) { return ((PGPSignatureList) obj).get(0); } return null; } } @Override public SignatureVerification verify(Repository repository, GpgConfig config, byte[] data, byte[] signatureData) throws IOException { PGPSignature signature = null; String fingerprint = null; String signer = null; String keyId = null; try (InputStream sigIn = new ByteArrayInputStream(signatureData)) { signature = parseSignature(sigIn); if (signature != null) { // Try to figure out something to find the public key with. if (signature.hasSubpackets()) { PGPSignatureSubpacketVector packets = signature .getHashedSubPackets(); IssuerFingerprint fingerprintPacket = packets .getIssuerFingerprint(); if (fingerprintPacket != null) { fingerprint = Hex .toHexString(fingerprintPacket.getFingerprint()) .toLowerCase(Locale.ROOT); } signer = packets.getSignerUserID(); if (signer != null) { signer = BouncyCastleGpgSigner.extractSignerId(signer); } } keyId = Long.toUnsignedString(signature.getKeyID(), 16) .toLowerCase(Locale.ROOT); } else { throw new JGitInternalException(BCText.get().nonSignatureError); } } catch (NumberFormatException | PGPException e) { throw new JGitInternalException(BCText.get().signatureParseError, e); } Date signatureCreatedAt = signature.getCreationTime(); if (fingerprint == null && signer == null && keyId == null) { return new SignatureVerification(NAME, signatureCreatedAt, null, null, null, false, false, TrustLevel.UNKNOWN, BCText.get().signatureNoKeyInfo); } if (fingerprint != null && keyId != null && !fingerprint.endsWith(keyId)) { return new SignatureVerification(NAME, signatureCreatedAt, signer, fingerprint, signer, false, false, TrustLevel.UNKNOWN, MessageFormat.format(BCText.get().signatureInconsistent, keyId, fingerprint)); } if (fingerprint == null && keyId != null) { fingerprint = keyId; } // Try to find the public key String keySpec = '<' + signer + '>'; Object cached = null; BouncyCastleGpgPublicKey publicKey = null; try { cached = byFingerprint.get(fingerprint); if (cached != null) { if (cached instanceof BouncyCastleGpgPublicKey) { publicKey = (BouncyCastleGpgPublicKey) cached; } } else if (!StringUtils.isEmptyOrNull(signer)) { cached = bySigner.get(signer); if (cached != null) { if (cached instanceof BouncyCastleGpgPublicKey) { publicKey = (BouncyCastleGpgPublicKey) cached; } } } if (cached == null) { publicKey = BouncyCastleGpgKeyLocator.findPublicKey(fingerprint, keySpec); } } catch (IOException | PGPException e) { throw new JGitInternalException( BCText.get().signatureKeyLookupError, e); } if (publicKey == null) { if (cached == null) { byFingerprint.put(fingerprint, NO_KEY); byFingerprint.put(keyId, NO_KEY); if (signer != null) { bySigner.put(signer, NO_KEY); } } return new SignatureVerification(NAME, signatureCreatedAt, signer, fingerprint, signer, false, false, TrustLevel.UNKNOWN, BCText.get().signatureNoPublicKey); } if (fingerprint != null && !publicKey.isExactMatch()) { // We did find _some_ signing key for the signer, but it doesn't // match the given fingerprint. return new SignatureVerification(NAME, signatureCreatedAt, signer, fingerprint, signer, false, false, TrustLevel.UNKNOWN, MessageFormat.format(BCText.get().signatureNoSigningKey, fingerprint)); } if (cached == null) { byFingerprint.put(fingerprint, publicKey); byFingerprint.put(keyId, publicKey); if (signer != null) { bySigner.put(signer, publicKey); } } String user = null; List userIds = publicKey.getUserIds(); if (userIds != null && !userIds.isEmpty()) { if (!StringUtils.isEmptyOrNull(signer)) { for (String userId : publicKey.getUserIds()) { if (BouncyCastleGpgKeyLocator.containsSigningKey(userId, keySpec)) { user = userId; break; } } } if (user == null) { user = userIds.get(0); } } else if (signer != null) { user = signer; } PGPPublicKey pubKey = publicKey.getPublicKey(); boolean expired = false; long validFor = pubKey.getValidSeconds(); if (validFor > 0 && signatureCreatedAt != null) { Instant expiredAt = pubKey.getCreationTime().toInstant() .plusSeconds(validFor); expired = expiredAt.isBefore(signatureCreatedAt.toInstant()); } // Trust data is not defined in OpenPGP; the format is implementation // specific. We don't use the GPG trustdb but simply the trust packet // on the public key, if present. Even if present, it may or may not // be set. byte[] trustData = pubKey.getTrustData(); TrustLevel trust = parseGpgTrustPacket(trustData); boolean verified = false; try { signature.init( new JcaPGPContentVerifierBuilderProvider(), pubKey); signature.update(data); verified = signature.verify(); } catch (PGPException e) { throw new JGitInternalException( BCText.get().signatureVerificationError, e); } return new SignatureVerification(NAME, signatureCreatedAt, signer, fingerprint, user, verified, expired, trust, null); } private TrustLevel parseGpgTrustPacket(byte[] packet) { if (packet == null || packet.length < 6) { // A GPG trust packet has at least 6 bytes. return TrustLevel.UNKNOWN; } if (packet[2] != 'g' || packet[3] != 'p' || packet[4] != 'g') { // Not a GPG trust packet return TrustLevel.UNKNOWN; } int trust = packet[0] & 0x0F; switch (trust) { case 0: // No determined/set case 1: // Trust expired; i.e., calculation outdated or key expired case 2: // Undefined: not enough information to set return TrustLevel.UNKNOWN; case 3: return TrustLevel.NEVER; case 4: return TrustLevel.MARGINAL; case 5: return TrustLevel.FULL; case 6: return TrustLevel.ULTIMATE; default: return TrustLevel.UNKNOWN; } } @Override public void clear() { byFingerprint.clear(); bySigner.clear(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 977 Content-Disposition: inline; filename="BouncyCastleGpgSignatureVerifierFactory.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "566ad1bf916b7385ad74ab9a90747a24d220e4c4" /* * Copyright (C) 2021, 2024 Thomas Wolf 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.gpg.bc.internal; import org.eclipse.jgit.lib.GpgConfig.GpgFormat; import org.eclipse.jgit.lib.SignatureVerifier; import org.eclipse.jgit.lib.SignatureVerifierFactory; /** * A {@link SignatureVerifierFactory} that creates {@link SignatureVerifier} * instances that verify GPG signatures using BouncyCastle and that do cache * public keys. */ public final class BouncyCastleGpgSignatureVerifierFactory implements SignatureVerifierFactory { @Override public GpgFormat getType() { return GpgFormat.OPENPGP; } @Override public SignatureVerifier create() { return new BouncyCastleGpgSignatureVerifier(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6540 Content-Disposition: inline; filename="BouncyCastleGpgSigner.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "adac9b199d5a4af6b8c2327a4a2431755aeef44d" /* * Copyright (C) 2018, 2024, 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.gpg.bc.internal; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.Iterator; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; 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.Nullable; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.GpgSignature; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Signer; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.util.StringUtils; /** * GPG Signer using the BouncyCastle library. */ public class BouncyCastleGpgSigner implements Signer { 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 GpgSignature sign(Repository repository, GpgConfig config, byte[] data, PersonIdent committer, String signingKey, CredentialsProvider credentialsProvider) throws CanceledException, IOException, UnsupportedSigningFormatException { String gpgSigningKey = signingKey; if (gpgSigningKey == null) { gpgSigningKey = config.getSigningKey(); } try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( credentialsProvider)) { BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, committer, passphrasePrompt); PGPSecretKey secretKey = gpgKey.getSecretKey(); if (secretKey == null) { throw new JGitInternalException( BCText.get().unableToSignCommitNoSecretKey); } JcePBESecretKeyDecryptorBuilder decryptorBuilder = new JcePBESecretKeyDecryptorBuilder(); PGPPrivateKey privateKey = null; if (!passphrasePrompt.hasPassphrase()) { // Either the key is not encrypted, or it was read from the // legacy secring.gpg. Try getting the private key without // passphrase first. try { privateKey = secretKey.extractPrivateKey( decryptorBuilder.build(new char[0])); } catch (PGPException e) { // Ignore and try again with passphrase below } } if (privateKey == null) { // Try using a passphrase char[] passphrase = passphrasePrompt.getPassphrase( secretKey.getPublicKey().getFingerprint(), gpgKey.getOrigin()); privateKey = secretKey .extractPrivateKey(decryptorBuilder.build(passphrase)); } PGPPublicKey publicKey = secretKey.getPublicKey(); PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( new JcaPGPContentSignerBuilder( publicKey.getAlgorithm(), HashAlgorithmTags.SHA256), publicKey); signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setIssuerFingerprint(false, publicKey); // Also add the signer's user ID. Note that GPG uses only the e-mail // address part. String userId = committer.getEmailAddress(); Iterator userIds = publicKey.getUserIDs(); if (userIds.hasNext()) { String keyUserId = userIds.next(); if (!StringUtils.isEmptyOrNull(keyUserId) && (userId == null || !keyUserId.contains(userId))) { // Not the committer's key? userId = extractSignerId(keyUserId); } } if (userId != null) { subpackets.addSignerUserID(false, userId); } signatureGenerator .setHashedSubpackets(subpackets.generate()); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try (BCPGOutputStream out = new BCPGOutputStream( new ArmoredOutputStream(buffer))) { signatureGenerator.update(data); signatureGenerator.generate().encode(out); } return new GpgSignature(buffer.toByteArray()); } catch (PGPException | NoSuchAlgorithmException | NoSuchProviderException | URISyntaxException e) { throw new JGitInternalException(e.getMessage(), e); } } @Override public boolean canLocateSigningKey(Repository repository, GpgConfig config, PersonIdent committer, String signingKey, CredentialsProvider credentialsProvider) throws CanceledException { String gpgSigningKey = signingKey; if (gpgSigningKey == null) { gpgSigningKey = config.getSigningKey(); } try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( credentialsProvider)) { BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, committer, passphrasePrompt); return gpgKey != null; } catch (CanceledException e) { throw e; } catch (Exception e) { return false; } } static String extractSignerId(String pgpUserId) { int from = pgpUserId.indexOf('<'); if (from >= 0) { int to = pgpUserId.indexOf('>', from + 1); if (to > from + 1) { return pgpUserId.substring(from + 1, to); } } return pgpUserId; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 828 Content-Disposition: inline; filename="BouncyCastleGpgSignerFactory.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "92ab65d7e44ff36f0acb5142b47f70af75bab539" /* * Copyright (C) 2021, 2024 Thomas Wolf 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.gpg.bc.internal; import org.eclipse.jgit.lib.GpgConfig.GpgFormat; import org.eclipse.jgit.lib.Signer; import org.eclipse.jgit.lib.SignerFactory; /** * Factory for creating a {@link Signer} for OPENPGP signatures based on Bouncy * Castle. */ public final class BouncyCastleGpgSignerFactory implements SignerFactory { @Override public GpgFormat getType() { return GpgFormat.OPENPGP; } @Override public Signer create() { return new BouncyCastleGpgSigner(); } } Content-Type: text/plain; charset=UTF-8 Content-Length: 828 Content-Disposition: inline; filename="BouncyCastleGpgSignerFactory.java" Last-Modified: Thu, 14 Aug 2025 05:02:44 GMT Expires: Thu, 14 Aug 2025 05:07:44 GMT ETag: "0968b6fcfe8d9dd3257a36788eb7b4ca735e81e7" /org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/

/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/