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;
return keyId;
}
- static PGPPublicKey findPublicKey(String fingerprint, String keySpec)
+ static BouncyCastleGpgPublicKey findPublicKey(String fingerprint,
+ String keySpec)
throws IOException, PGPException {
- PGPPublicKey result = findPublicKeyInPubring(USER_PGP_PUBRING_FILE,
+ BouncyCastleGpgPublicKey result = findPublicKeyInPubring(
+ USER_PGP_PUBRING_FILE,
fingerprint, keySpec);
if (result == null && exists(USER_KEYBOX_PATH)) {
try {
* @throws NoOpenPgpKeyException
* if the file does not contain any OpenPGP key
*/
- private static PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile,
+ private static BouncyCastleGpgPublicKey findPublicKeyInKeyBox(
+ Path keyboxFile,
String keyId, String keySpec)
throws IOException, NoSuchAlgorithmException,
NoSuchProviderException, NoOpenPgpKeyException {
hasOpenPgpKey = true;
PGPPublicKey key = findPublicKeyByKeyId(keyBlob, id);
if (key != null) {
- return key;
+ if (!isSigningKey(key)) {
+ return null;
+ }
+ return new BouncyCastleGpgPublicKey(key, true,
+ toStrings(keyBlob.getUserIds()));
}
key = findPublicKeyByUserId(keyBlob, keySpec);
if (key != null) {
- return key;
+ return new BouncyCastleGpgPublicKey(key, true,
+ toStrings(keyBlob.getUserIds()));
}
}
}
return null;
}
+ private static List<String> toStrings(List<UserID> 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
NoSuchAlgorithmException, NoSuchProviderException, PGPException,
CanceledException, UnsupportedCredentialItem, URISyntaxException {
BouncyCastleGpgKey key;
- PGPPublicKey publicKey = null;
+ 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
publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH, null,
signingKey);
if (publicKey != null) {
- key = findSecretKeyForKeyBoxPublicKey(publicKey,
- USER_KEYBOX_PATH);
+ key = findSecretKeyForKeyBoxPublicKey(
+ publicKey.getPublicKey(), USER_KEYBOX_PATH);
if (key != null) {
return key;
}
throw new PGPException(MessageFormat.format(
BCText.get().gpgNoSecretKeyForPublicKey,
- Long.toHexString(publicKey.getKeyID())));
+ Long.toHexString(
+ publicKey.getPublicKey().getKeyID())));
}
throw new PGPException(MessageFormat.format(
BCText.get().gpgNoPublicKeyFound, signingKey));
// 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,
+ key = findSecretKeyForKeyBoxPublicKey(
+ publicKey.getPublicKey(),
USER_PGP_PUBRING_FILE);
if (key != null) {
return key;
if (publicKey != null) {
throw new PGPException(MessageFormat.format(
BCText.get().gpgNoSecretKeyForPublicKey,
- Long.toHexString(publicKey.getKeyID())));
+ Long.toHexString(publicKey.getPublicKey().getKeyID())));
} else if (hasSecring) {
// publicKey == null: user has _only_ pubring.gpg/secring.gpg.
throw new PGPException(MessageFormat.format(
* @throws PGPException
* on BouncyCastle errors
*/
- private static PGPPublicKey findPublicKeyInPubring(Path pubringFile,
+ 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<PGPPublicKeyRing> keyrings = pgpPub.getKeyRings();
+ BouncyCastleGpgPublicKey candidate = null;
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(id)) {
- return key;
- }
- // try user id
- Iterator<String> userIDs = key.getUserIDs();
- while (userIDs.hasNext()) {
- String userId = userIDs.next();
- if (containsSigningKey(userId, keySpec)) {
- return key;
- }
+ 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) {
- // Ignore and return null
+ return null;
+ }
+ }
+
+ private static BouncyCastleGpgPublicKey findPublicKeyInPubring(
+ PGPPublicKeyRing keyRing, String keyId, String keySpec) {
+ Iterator<PGPPublicKey> 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<String> 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;
}
import java.text.MessageFormat;
import java.time.Instant;
import java.util.Date;
-import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
import org.bouncycastle.bcpg.sig.IssuerFingerprint;
} else {
throw new JGitInternalException(BCText.get().nonSignatureError);
}
- } catch (PGPException e) {
+ } catch (NumberFormatException | PGPException e) {
throw new JGitInternalException(BCText.get().signatureParseError,
e);
}
if (fingerprint != null && keyId != null
&& !fingerprint.endsWith(keyId)) {
return new VerificationResult(signatureCreatedAt, signer, fingerprint,
- null, false, false, TrustLevel.UNKNOWN,
+ signer, false, false, TrustLevel.UNKNOWN,
MessageFormat.format(BCText.get().signatureInconsistent,
keyId, fingerprint));
}
// Try to find the public key
String keySpec = '<' + signer + '>';
Object cached = null;
- PGPPublicKey publicKey = null;
+ BouncyCastleGpgPublicKey publicKey = null;
try {
cached = byFingerprint.get(fingerprint);
if (cached != null) {
- if (cached instanceof PGPPublicKey) {
- publicKey = (PGPPublicKey) cached;
+ if (cached instanceof BouncyCastleGpgPublicKey) {
+ publicKey = (BouncyCastleGpgPublicKey) cached;
}
} else if (!StringUtils.isEmptyOrNull(signer)) {
cached = bySigner.get(signer);
if (cached != null) {
- if (cached instanceof PGPPublicKey) {
- publicKey = (PGPPublicKey) cached;
+ if (cached instanceof BouncyCastleGpgPublicKey) {
+ publicKey = (BouncyCastleGpgPublicKey) cached;
}
}
}
}
}
return new VerificationResult(signatureCreatedAt, signer,
- fingerprint, null, false, false, TrustLevel.UNKNOWN,
+ 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 VerificationResult(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);
}
}
String user = null;
- Iterator<String> userIds = publicKey.getUserIDs();
- if (!StringUtils.isEmptyOrNull(signer)) {
- while (userIds.hasNext()) {
- String userId = userIds.next();
- if (BouncyCastleGpgKeyLocator.containsSigningKey(userId,
- keySpec)) {
- user = userId;
- break;
+ List<String> 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) {
- userIds = publicKey.getUserIDs();
- if (userIds.hasNext()) {
- user = userIds.next();
+ if (user == null) {
+ user = userIds.get(0);
}
+ } else if (signer != null) {
+ user = signer;
}
+ PGPPublicKey pubKey = publicKey.getPublicKey();
boolean expired = false;
- long validFor = publicKey.getValidSeconds();
+ long validFor = pubKey.getValidSeconds();
if (validFor > 0 && signatureCreatedAt != null) {
- Instant expiredAt = publicKey.getCreationTime().toInstant()
+ Instant expiredAt = pubKey.getCreationTime().toInstant()
.plusSeconds(validFor);
expired = expiredAt.isBefore(signatureCreatedAt.toInstant());
}
// 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 = publicKey.getTrustData();
+ byte[] trustData = pubKey.getTrustData();
TrustLevel trust = parseGpgTrustPacket(trustData);
boolean verified = false;
try {
signature.init(
new JcaPGPContentVerifierBuilderProvider()
.setProvider(BouncyCastleProvider.PROVIDER_NAME),
- publicKey);
+ pubKey);
signature.update(data);
verified = signature.verify();
} catch (PGPException e) {