diff options
36 files changed, 943 insertions, 1043 deletions
diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF index 9749ac1111..7a62d1d232 100644 --- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF @@ -28,9 +28,6 @@ Import-Package: org.bouncycastle.asn1;version="[1.69.0,2.0.0)", org.bouncycastle.util;version="[1.69.0,2.0.0)", org.bouncycastle.util.encoders;version="[1.69.0,2.0.0)", org.bouncycastle.util.io;version="[1.69.0,2.0.0)", - org.eclipse.jgit.annotations;version="[7.0.0,7.1.0)", - org.eclipse.jgit.api.errors;version="[7.0.0,7.1.0)", org.slf4j;version="[1.7.0,3.0.0)" -Export-Package: org.eclipse.jgit.gpg.bc;version="7.0.0", - org.eclipse.jgit.gpg.bc.internal;version="7.0.0";x-friends:="org.eclipse.jgit.gpg.bc.test", +Export-Package: org.eclipse.jgit.gpg.bc.internal;version="7.0.0";x-friends:="org.eclipse.jgit.gpg.bc.test", org.eclipse.jgit.gpg.bc.internal.keys;version="7.0.0";x-friends:="org.eclipse.jgit.gpg.bc.test" diff --git a/org.eclipse.jgit.gpg.bc/pom.xml b/org.eclipse.jgit.gpg.bc/pom.xml index f4dce68fab..ad907d3809 100644 --- a/org.eclipse.jgit.gpg.bc/pom.xml +++ b/org.eclipse.jgit.gpg.bc/pom.xml @@ -160,7 +160,7 @@ <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> <onlyBinaryIncompatible>false</onlyBinaryIncompatible> <includeSynthetic>false</includeSynthetic> - <ignoreMissingClasses>false</ignoreMissingClasses> + <ignoreMissingClasses>true</ignoreMissingClasses> <skipPomModules>true</skipPomModules> </parameter> <skip>false</skip> diff --git a/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSigner b/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSigner deleted file mode 100644 index 6752b64ddf..0000000000 --- a/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSigner +++ /dev/null @@ -1 +0,0 @@ -org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner diff --git a/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory b/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory index 17ab30fba7..17ab30fba7 100644 --- a/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory +++ b/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory diff --git a/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.SignerFactory b/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.SignerFactory new file mode 100644 index 0000000000..c0b214db39 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.SignerFactory @@ -0,0 +1 @@ +org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSignerFactory diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java deleted file mode 100644 index fdd1a2b11a..0000000000 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> 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; - -import org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner; -import org.eclipse.jgit.lib.GpgSigner; - -/** - * Factory for creating a {@link GpgSigner} based on Bouncy Castle. - * - * @since 5.11 - */ -public final class BouncyCastleGpgSignerFactory { - - private BouncyCastleGpgSignerFactory() { - // No instantiation - } - - /** - * Creates a new {@link GpgSigner}. - * - * @return the {@link GpgSigner} - */ - public static GpgSigner create() { - return new BouncyCastleGpgSigner(); - } -} diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java index 3378bb3969..5a3d43ba54 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java @@ -12,7 +12,6 @@ package org.eclipse.jgit.gpg.bc.internal; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.security.Security; import java.text.MessageFormat; import java.time.Instant; import java.util.Date; @@ -20,7 +19,6 @@ import java.util.List; import java.util.Locale; import org.bouncycastle.bcpg.sig.IssuerFingerprint; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; @@ -31,33 +29,20 @@ 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.annotations.NonNull; import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.lib.AbstractGpgSignatureVerifier; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgSignatureVerifier; +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 GpgSignatureVerifier} to verify GPG signatures using BouncyCastle. + * A {@link SignatureVerifier} to verify GPG signatures using BouncyCastle. */ public class BouncyCastleGpgSignatureVerifier - extends AbstractGpgSignatureVerifier { + implements SignatureVerifier { - private static void registerBouncyCastleProviderIfNecessary() { - if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { - Security.addProvider(new BouncyCastleProvider()); - } - } - - /** - * Creates a new instance and registers the BouncyCastle security provider - * if needed. - */ - public BouncyCastleGpgSignatureVerifier() { - registerBouncyCastleProviderIfNecessary(); - } + 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. @@ -70,7 +55,7 @@ public class BouncyCastleGpgSignatureVerifier @Override public String getName() { - return "bc"; //$NON-NLS-1$ + return NAME; } static PGPSignature parseSignature(InputStream in) @@ -90,9 +75,8 @@ public class BouncyCastleGpgSignatureVerifier } @Override - public SignatureVerification verify(@NonNull GpgConfig config, byte[] data, - byte[] signatureData) - throws IOException { + public SignatureVerification verify(Repository repository, GpgConfig config, + byte[] data, byte[] signatureData) throws IOException { PGPSignature signature = null; String fingerprint = null; String signer = null; @@ -127,14 +111,15 @@ public class BouncyCastleGpgSignatureVerifier } Date signatureCreatedAt = signature.getCreationTime(); if (fingerprint == null && signer == null && keyId == null) { - return new VerificationResult(signatureCreatedAt, null, null, null, - false, false, TrustLevel.UNKNOWN, + 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 VerificationResult(signatureCreatedAt, signer, fingerprint, - signer, false, false, TrustLevel.UNKNOWN, + return new SignatureVerification(NAME, signatureCreatedAt, + signer, fingerprint, signer, false, false, + TrustLevel.UNKNOWN, MessageFormat.format(BCText.get().signatureInconsistent, keyId, fingerprint)); } @@ -175,15 +160,16 @@ public class BouncyCastleGpgSignatureVerifier bySigner.put(signer, NO_KEY); } } - return new VerificationResult(signatureCreatedAt, signer, - fingerprint, signer, false, false, TrustLevel.UNKNOWN, - BCText.get().signatureNoPublicKey); + 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 VerificationResult(signatureCreatedAt, signer, - fingerprint, signer, false, false, TrustLevel.UNKNOWN, + return new SignatureVerification(NAME, signatureCreatedAt, + signer, fingerprint, signer, false, false, + TrustLevel.UNKNOWN, MessageFormat.format(BCText.get().signatureNoSigningKey, fingerprint)); } @@ -229,8 +215,7 @@ public class BouncyCastleGpgSignatureVerifier boolean verified = false; try { signature.init( - new JcaPGPContentVerifierBuilderProvider() - .setProvider(BouncyCastleProvider.PROVIDER_NAME), + new JcaPGPContentVerifierBuilderProvider(), pubKey); signature.update(data); verified = signature.verify(); @@ -238,15 +223,8 @@ public class BouncyCastleGpgSignatureVerifier throw new JGitInternalException( BCText.get().signatureVerificationError, e); } - return new VerificationResult(signatureCreatedAt, signer, fingerprint, user, - verified, expired, trust, null); - } - - @Override - public SignatureVerification verify(byte[] data, byte[] signatureData) - throws IOException { - throw new UnsupportedOperationException( - "Call verify(GpgConfig, byte[], byte[]) instead."); //$NON-NLS-1$ + return new SignatureVerification(NAME, signatureCreatedAt, signer, + fingerprint, user, verified, expired, trust, null); } private TrustLevel parseGpgTrustPacket(byte[] packet) { @@ -282,76 +260,4 @@ public class BouncyCastleGpgSignatureVerifier byFingerprint.clear(); bySigner.clear(); } - - private static class VerificationResult implements SignatureVerification { - - private final Date creationDate; - - private final String signer; - - private final String keyUser; - - private final String fingerprint; - - private final boolean verified; - - private final boolean expired; - - private final @NonNull TrustLevel trustLevel; - - private final String message; - - public VerificationResult(Date creationDate, String signer, - String fingerprint, String user, boolean verified, - boolean expired, @NonNull TrustLevel trust, String message) { - this.creationDate = creationDate; - this.signer = signer; - this.fingerprint = fingerprint; - this.keyUser = user; - this.verified = verified; - this.expired = expired; - this.trustLevel = trust; - this.message = message; - } - - @Override - public Date getCreationDate() { - return creationDate; - } - - @Override - public String getSigner() { - return signer; - } - - @Override - public String getKeyUser() { - return keyUser; - } - - @Override - public String getKeyFingerprint() { - return fingerprint; - } - - @Override - public boolean isExpired() { - return expired; - } - - @Override - public TrustLevel getTrustLevel() { - return trustLevel; - } - - @Override - public String getMessage() { - return message; - } - - @Override - public boolean getVerified() { - return verified; - } - } } diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java index ae82b758a6..566ad1bf91 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others + * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> 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 @@ -9,20 +9,27 @@ */ package org.eclipse.jgit.gpg.bc.internal; -import org.eclipse.jgit.lib.GpgSignatureVerifier; -import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; +import org.eclipse.jgit.lib.GpgConfig.GpgFormat; +import org.eclipse.jgit.lib.SignatureVerifier; +import org.eclipse.jgit.lib.SignatureVerifierFactory; /** - * A {@link GpgSignatureVerifierFactory} that creates - * {@link GpgSignatureVerifier} instances that verify GPG signatures using - * BouncyCastle and that do cache public keys. + * A {@link SignatureVerifierFactory} that creates {@link SignatureVerifier} + * instances that verify GPG signatures using BouncyCastle and that do cache + * public keys. */ public final class BouncyCastleGpgSignatureVerifierFactory - extends GpgSignatureVerifierFactory { + implements SignatureVerifierFactory { @Override - public GpgSignatureVerifier getVerifier() { + public GpgFormat getType() { + return GpgFormat.OPENPGP; + } + + @Override + public SignatureVerifier create() { return new BouncyCastleGpgSignatureVerifier(); } + } diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java index 763b7f7526..1d187a5db2 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2021, Salesforce and others + * 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 @@ -14,13 +14,11 @@ import java.io.IOException; import java.net.URISyntaxException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.security.Security; import java.util.Iterator; 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.PGPPublicKey; @@ -30,79 +28,23 @@ 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.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgObjectSigner; import org.eclipse.jgit.lib.GpgSignature; -import org.eclipse.jgit.lib.GpgSigner; -import org.eclipse.jgit.lib.ObjectBuilder; import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.GpgConfig.GpgFormat; +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 extends GpgSigner - implements GpgObjectSigner { - - 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 { - return canLocateSigningKey(gpgSigningKey, committer, - credentialsProvider, null); - } catch (UnsupportedSigningFormatException e) { - // Cannot occur with a null config - return false; - } - } - - @Override - public boolean canLocateSigningKey(@Nullable String gpgSigningKey, - PersonIdent committer, CredentialsProvider credentialsProvider, - GpgConfig config) - throws CanceledException, UnsupportedSigningFormatException { - if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) { - throw new UnsupportedSigningFormatException( - JGitText.get().onlyOpenPgpSupportedForSigning); - } - 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; - } - } +public class BouncyCastleGpgSigner implements Signer { private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey, PersonIdent committer, @@ -121,38 +63,24 @@ public class BouncyCastleGpgSigner extends GpgSigner } @Override - public void sign(@NonNull CommitBuilder commit, - @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider) throws CanceledException { - try { - signObject(commit, gpgSigningKey, committer, credentialsProvider, - null); - } catch (UnsupportedSigningFormatException e) { - // Cannot occur with a null config - } - } - - @Override - public void signObject(@NonNull ObjectBuilder object, - @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider, GpgConfig config) - throws CanceledException, UnsupportedSigningFormatException { - if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) { - throw new UnsupportedSigningFormatException( - JGitText.get().onlyOpenPgpSupportedForSigning); + 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); + committer, passphrasePrompt); PGPSecretKey secretKey = gpgKey.getSecretKey(); if (secretKey == null) { throw new JGitInternalException( BCText.get().unableToSignCommitNoSecretKey); } - JcePBESecretKeyDecryptorBuilder decryptorBuilder = new JcePBESecretKeyDecryptorBuilder() - .setProvider(BouncyCastleProvider.PROVIDER_NAME); + JcePBESecretKeyDecryptorBuilder decryptorBuilder = new JcePBESecretKeyDecryptorBuilder(); PGPPrivateKey privateKey = null; if (!passphrasePrompt.hasPassphrase()) { // Either the key is not encrypted, or it was read from the @@ -177,8 +105,7 @@ public class BouncyCastleGpgSigner extends GpgSigner PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( new JcaPGPContentSignerBuilder( publicKey.getAlgorithm(), - HashAlgorithmTags.SHA256).setProvider( - BouncyCastleProvider.PROVIDER_NAME)); + HashAlgorithmTags.SHA256)); signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setIssuerFingerprint(false, publicKey); @@ -202,16 +129,36 @@ public class BouncyCastleGpgSigner extends GpgSigner ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try (BCPGOutputStream out = new BCPGOutputStream( new ArmoredOutputStream(buffer))) { - signatureGenerator.update(object.build()); + signatureGenerator.update(data); signatureGenerator.generate().encode(out); } - object.setGpgSignature(new GpgSignature(buffer.toByteArray())); - } catch (PGPException | IOException | NoSuchAlgorithmException + 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) { diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignerFactory.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignerFactory.java new file mode 100644 index 0000000000..92ab65d7e4 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignerFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> 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(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java index 852a4b377b..958e566986 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java @@ -32,13 +32,12 @@ import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgSignatureVerifier; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; -import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SignatureVerifiers; import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.internal.VerificationUtils; @@ -174,8 +173,6 @@ class Log extends RevWalkTextBuiltin { // END -- Options shared with Diff - private GpgSignatureVerifier verifier; - private GpgConfig config; Log() { @@ -227,9 +224,6 @@ class Log extends RevWalkTextBuiltin { throw die(e.getMessage(), e); } finally { diffFmt.close(); - if (verifier != null) { - verifier.clear(); - } } } @@ -293,21 +287,13 @@ class Log extends RevWalkTextBuiltin { if (c.getRawGpgSignature() == null) { return; } - if (verifier == null) { - GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory - .getDefault(); - if (factory == null) { - throw die(CLIText.get().logNoSignatureVerifier, null); - } - verifier = factory.getVerifier(); - } - SignatureVerification verification = verifier.verifySignature(c, - config); + SignatureVerification verification = SignatureVerifiers.verify(db, + config, c); if (verification == null) { return; } VerificationUtils.writeVerification(outw, verification, - verifier.getName(), c.getCommitterIdent()); + verification.verifierName(), c.getCommitterIdent()); } /** diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java index 4feb090032..1576792234 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java @@ -30,12 +30,11 @@ import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgSignatureVerifier; -import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.SignatureVerifiers; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.internal.VerificationUtils; import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; @@ -335,23 +334,13 @@ class Show extends TextBuiltin { if (c.getRawGpgSignature() == null) { return; } - GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory - .getDefault(); - if (factory == null) { - throw die(CLIText.get().logNoSignatureVerifier, null); - } - GpgSignatureVerifier verifier = factory.getVerifier(); GpgConfig config = new GpgConfig(db.getConfig()); - try { - SignatureVerification verification = verifier.verifySignature(c, - config); - if (verification == null) { - return; - } - VerificationUtils.writeVerification(outw, verification, - verifier.getName(), c.getCommitterIdent()); - } finally { - verifier.clear(); + SignatureVerification verification = SignatureVerifiers.verify(db, + config, c); + if (verification == null) { + throw die(CLIText.get().logNoSignatureVerifier, null); } + VerificationUtils.writeVerification(outw, verification, + verification.verifierName(), c.getCommitterIdent()); } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java index 4ea67ab92c..6be30c9447 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java @@ -27,10 +27,10 @@ import org.eclipse.jgit.api.VerifySignatureCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.internal.VerificationUtils; import org.eclipse.jgit.revwalk.RevCommit; @@ -106,7 +106,8 @@ class Tag extends TextBuiltin { if (error != null) { throw die(error.getMessage(), error); } - writeVerification(verifySig.getVerifier().getName(), + writeVerification( + verification.getVerification().verifierName(), (RevTag) verification.getObject(), verification.getVerification()); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java index c1f8a86a8c..64ee602620 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java @@ -11,7 +11,7 @@ package org.eclipse.jgit.pgm.internal; import java.io.IOException; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.util.GitDateFormatter; import org.eclipse.jgit.util.SignatureUtils; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java index 35de73e204..e74e234297 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.EmptyCommitException; +import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.dircache.DirCache; @@ -34,19 +35,23 @@ import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.time.TimeUtil; -import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.GpgSigner; +import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgConfig.GpgFormat; +import org.eclipse.jgit.lib.GpgSignature; +import org.eclipse.jgit.lib.ObjectBuilder; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.Signer; +import org.eclipse.jgit.lib.Signers; import org.eclipse.jgit.lib.StoredConfig; -import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.submodule.SubmoduleWalk; @@ -839,21 +844,39 @@ public class CommitCommandTest extends RepositoryTestCase { String[] signingKey = new String[1]; PersonIdent[] signingCommitters = new PersonIdent[1]; AtomicInteger callCount = new AtomicInteger(); - GpgSigner.setDefault(new GpgSigner() { + // Since GpgFormat defaults to OpenPGP just set a new signer for + // that. + Signers.set(GpgFormat.OPENPGP, new Signer() { + @Override - public void sign(CommitBuilder commit, String gpgSigningKey, - PersonIdent signingCommitter, CredentialsProvider credentialsProvider) { - signingKey[0] = gpgSigningKey; + public void signObject(Repository repo, GpgConfig config, + ObjectBuilder builder, PersonIdent signingCommitter, + String signingKeySpec, + CredentialsProvider credentialsProvider) + throws CanceledException, + UnsupportedSigningFormatException { + signingKey[0] = signingKeySpec; signingCommitters[0] = signingCommitter; callCount.incrementAndGet(); } @Override - public boolean canLocateSigningKey(String gpgSigningKey, - PersonIdent signingCommitter, + public GpgSignature sign(Repository repo, GpgConfig config, + byte[] data, PersonIdent signingCommitter, + String signingKeySpec, + CredentialsProvider credentialsProvider) + throws CanceledException, + UnsupportedSigningFormatException { + throw new CanceledException("Unexpected call"); + } + + @Override + public boolean canLocateSigningKey(Repository repo, + GpgConfig config, PersonIdent signingCommitter, + String signingKeySpec, CredentialsProvider credentialsProvider) throws CanceledException { - return false; + throw new CanceledException("Unexpected call"); } }); @@ -904,19 +927,37 @@ public class CommitCommandTest extends RepositoryTestCase { git.add().addFilepattern("file1").call(); AtomicInteger callCount = new AtomicInteger(); - GpgSigner.setDefault(new GpgSigner() { + // Since GpgFormat defaults to OpenPGP just set a new signer for + // that. + Signers.set(GpgFormat.OPENPGP, new Signer() { + @Override - public void sign(CommitBuilder commit, String gpgSigningKey, - PersonIdent signingCommitter, CredentialsProvider credentialsProvider) { + public void signObject(Repository repo, GpgConfig config, + ObjectBuilder builder, PersonIdent signingCommitter, + String signingKeySpec, + CredentialsProvider credentialsProvider) + throws CanceledException, + UnsupportedSigningFormatException { callCount.incrementAndGet(); } @Override - public boolean canLocateSigningKey(String gpgSigningKey, - PersonIdent signingCommitter, + public GpgSignature sign(Repository repo, GpgConfig config, + byte[] data, PersonIdent signingCommitter, + String signingKeySpec, + CredentialsProvider credentialsProvider) + throws CanceledException, + UnsupportedSigningFormatException { + throw new CanceledException("Unexpected call"); + } + + @Override + public boolean canLocateSigningKey(Repository repo, + GpgConfig config, PersonIdent signingCommitter, + String signingKeySpec, CredentialsProvider credentialsProvider) throws CanceledException { - return false; + throw new CanceledException("Unexpected call"); } }); diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index c9f7336609..11435b8ce4 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -576,7 +576,6 @@ obtainingCommitsForCherryPick=Obtaining commits that need to be cherry-picked oldIdMustNotBeNull=Expected old ID must not be null onlyOneFetchSupported=Only one fetch supported onlyOneOperationCallPerConnectionIsSupported=Only one operation call per connection is supported. -onlyOpenPgpSupportedForSigning=OpenPGP is the only supported signing option with JGit at this time (gpg.format must be set to openpgp). openFilesMustBeAtLeast1=Open files must be >= 1 openingConnection=Opening connection operationCanceled=Operation {0} was canceled @@ -723,6 +722,8 @@ shortSkipOfBlock=Short skip of block. shutdownCleanup=Cleanup {} during JVM shutdown shutdownCleanupFailed=Cleanup during JVM shutdown failed shutdownCleanupListenerFailed=Cleanup of {0} during JVM shutdown failed +signatureServiceConflict={0} conflict for type {1}. Already registered is {2}; additional factory {3} is ignored. +signatureTypeUnknown=No signer for {0} signatures. Use another signature type for git config gpg.format, or do not sign. signatureVerificationError=Signature verification failed signatureVerificationUnavailable=No signature verifier registered signedTagMessageNoLf=A non-empty message of a signed tag must end in LF. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index a1a2cc09d2..a7d409c3f5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -51,9 +51,6 @@ import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgConfig.GpgFormat; -import org.eclipse.jgit.lib.GpgObjectSigner; -import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; @@ -62,6 +59,8 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.Signer; +import org.eclipse.jgit.lib.Signers; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; @@ -129,7 +128,7 @@ public class CommitCommand extends GitCommand<RevCommit> { private String signingKey; - private GpgSigner gpgSigner; + private Signer signer; private GpgConfig gpgConfig; @@ -319,30 +318,22 @@ public class CommitCommand extends GitCommand<RevCommit> { } } - private void sign(CommitBuilder commit) throws ServiceUnavailableException, - CanceledException, UnsupportedSigningFormatException { - if (gpgSigner == null) { - gpgSigner = GpgSigner.getDefault(); - if (gpgSigner == null) { - throw new ServiceUnavailableException( - JGitText.get().signingServiceUnavailable); + private void sign(CommitBuilder commit) + throws CanceledException, IOException, + UnsupportedSigningFormatException { + if (signer == null) { + signer = Signers.get(gpgConfig.getKeyFormat()); + if (signer == null) { + throw new UnsupportedSigningFormatException(MessageFormat + .format(JGitText.get().signatureTypeUnknown, + gpgConfig.getKeyFormat().toConfigValue())); } } if (signingKey == null) { signingKey = gpgConfig.getSigningKey(); } - if (gpgSigner instanceof GpgObjectSigner) { - ((GpgObjectSigner) gpgSigner).signObject(commit, - signingKey, committer, credentialsProvider, - gpgConfig); - } else { - if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) { - throw new UnsupportedSigningFormatException(JGitText - .get().onlyOpenPgpSupportedForSigning); - } - gpgSigner.sign(commit, signingKey, committer, - credentialsProvider); - } + signer.signObject(repo, gpgConfig, commit, committer, signingKey, + credentialsProvider); } private void updateRef(RepositoryState state, ObjectId headId, @@ -1097,22 +1088,22 @@ public class CommitCommand extends GitCommand<RevCommit> { } /** - * Sets the {@link GpgSigner} to use if the commit is to be signed. + * Sets the {@link Signer} to use if the commit is to be signed. * * @param signer * to use; if {@code null}, the default signer will be used * @return {@code this} - * @since 5.11 + * @since 7.0 */ - public CommitCommand setGpgSigner(GpgSigner signer) { + public CommitCommand setSigner(Signer signer) { checkCallable(); - this.gpgSigner = signer; + this.signer = signer; return this; } /** * Sets an external {@link GpgConfig} to use. Whether it will be used is at - * the discretion of the {@link #setGpgSigner(GpgSigner)}. + * the discretion of the {@link #setSigner(Signer)}. * * @param config * to set; if {@code null}, the config will be loaded from the diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java index 3edaf5e748..cc8589fa1c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java @@ -18,14 +18,11 @@ import org.eclipse.jgit.api.errors.InvalidTagNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; -import org.eclipse.jgit.api.errors.ServiceUnavailableException; import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.GpgConfig.GpgFormat; -import org.eclipse.jgit.lib.GpgObjectSigner; -import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; @@ -33,6 +30,8 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.Signer; +import org.eclipse.jgit.lib.Signers; import org.eclipse.jgit.lib.TagBuilder; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; @@ -79,7 +78,7 @@ public class TagCommand extends GitCommand<Ref> { private GpgConfig gpgConfig; - private GpgObjectSigner gpgSigner; + private Signer signer; private CredentialsProvider credentialsProvider; @@ -133,9 +132,9 @@ public class TagCommand extends GitCommand<Ref> { newTag.setTagger(tagger); newTag.setObjectId(id); - if (gpgSigner != null) { - gpgSigner.signObject(newTag, signingKey, tagger, - credentialsProvider, gpgConfig); + if (signer != null) { + signer.signObject(repo, gpgConfig, newTag, tagger, signingKey, + credentialsProvider); } // write the tag object @@ -196,15 +195,12 @@ public class TagCommand extends GitCommand<Ref> { * * @throws InvalidTagNameException * if the tag name is null or invalid - * @throws ServiceUnavailableException - * if the tag should be signed but no signer can be found * @throws UnsupportedSigningFormatException * if the tag should be signed but {@code gpg.format} is not * {@link GpgFormat#OPENPGP} */ private void processOptions() - throws InvalidTagNameException, ServiceUnavailableException, - UnsupportedSigningFormatException { + throws InvalidTagNameException, UnsupportedSigningFormatException { if (name == null || !Repository.isValidRefName(Constants.R_TAGS + name)) { throw new InvalidTagNameException( @@ -230,16 +226,15 @@ public class TagCommand extends GitCommand<Ref> { doSign = gpgConfig.isSignAnnotated(); } if (doSign) { - if (signingKey == null) { - signingKey = gpgConfig.getSigningKey(); - } - if (gpgSigner == null) { - GpgSigner signer = GpgSigner.getDefault(); - if (!(signer instanceof GpgObjectSigner)) { - throw new ServiceUnavailableException( - JGitText.get().signingServiceUnavailable); + if (signer == null) { + signer = Signers.get(gpgConfig.getKeyFormat()); + if (signer == null) { + throw new UnsupportedSigningFormatException( + MessageFormat.format( + JGitText.get().signatureTypeUnknown, + gpgConfig.getKeyFormat() + .toConfigValue())); } - gpgSigner = (GpgObjectSigner) signer; } // The message of a signed tag must end in a newline because // the signature will be appended. @@ -326,22 +321,22 @@ public class TagCommand extends GitCommand<Ref> { } /** - * Sets the {@link GpgSigner} to use if the commit is to be signed. + * Sets the {@link Signer} to use if the commit is to be signed. * * @param signer * to use; if {@code null}, the default signer will be used * @return {@code this} - * @since 5.11 + * @since 7.0 */ - public TagCommand setGpgSigner(GpgObjectSigner signer) { + public TagCommand setSigner(Signer signer) { checkCallable(); - this.gpgSigner = signer; + this.signer = signer; return this; } /** * Sets an external {@link GpgConfig} to use. Whether it will be used is at - * the discretion of the {@link #setGpgSigner(GpgObjectSigner)}. + * the discretion of the {@link #setSigner(Signer)}. * * @param config * to set; if {@code null}, the config will be loaded from the diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java index 21cddf75b7..f5f4b06e45 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java @@ -9,7 +9,7 @@ */ package org.eclipse.jgit.api; -import org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.lib.SignatureVerifier; import org.eclipse.jgit.revwalk.RevObject; /** @@ -34,8 +34,9 @@ public interface VerificationResult { * Retrieves the signature verification result. * * @return the result, or {@code null} if none was computed + * @since 7.0 */ - GpgSignatureVerifier.SignatureVerification getVerification(); + SignatureVerifier.SignatureVerification getVerification(); /** * Retrieves the git object of which the signature was verified. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java index 6a2a44ea2d..487ff04323 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java @@ -25,11 +25,10 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; -import org.eclipse.jgit.lib.GpgSignatureVerifier; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; -import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.SignatureVerifiers; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; @@ -65,12 +64,8 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR private VerifyMode mode = VerifyMode.ANY; - private GpgSignatureVerifier verifier; - private GpgConfig config; - private boolean ownVerifier; - /** * Creates a new {@link VerifySignatureCommand} for the given {@link Repository}. * @@ -140,22 +135,7 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } /** - * Sets the {@link GpgSignatureVerifier} to use. - * - * @param verifier - * the {@link GpgSignatureVerifier} to use, or {@code null} to - * use the default verifier - * @return {@code this} - */ - public VerifySignatureCommand setVerifier(GpgSignatureVerifier verifier) { - checkCallable(); - this.verifier = verifier; - return this; - } - - /** - * Sets an external {@link GpgConfig} to use. Whether it will be used it at - * the discretion of the {@link #setVerifier(GpgSignatureVerifier)}. + * Sets an external {@link GpgConfig} to use. * * @param config * to set; if {@code null}, the config will be loaded from the @@ -170,16 +150,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } /** - * Retrieves the currently set {@link GpgSignatureVerifier}. Can be used - * after a successful {@link #call()} to get the verifier that was used. - * - * @return the {@link GpgSignatureVerifier} - */ - public GpgSignatureVerifier getVerifier() { - return verifier; - } - - /** * {@link Repository#resolve(String) Resolves} all names added to the * command to git objects and verifies their signature. Non-existing objects * are ignored. @@ -193,9 +163,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR * * @return a map of the given names to the corresponding * {@link VerificationResult}, excluding ignored or skipped objects. - * @throws ServiceUnavailableException - * if no {@link GpgSignatureVerifier} was set and no - * {@link GpgSignatureVerifierFactory} is available * @throws WrongObjectTypeException * if a name resolves to an object of a type not allowed by the * {@link #setMode(VerifyMode)} mode @@ -207,16 +174,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR checkCallable(); setCallable(false); Map<String, VerificationResult> result = new HashMap<>(); - if (verifier == null) { - GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory - .getDefault(); - if (factory == null) { - throw new ServiceUnavailableException( - JGitText.get().signatureVerificationUnavailable); - } - verifier = factory.getVerifier(); - ownVerifier = true; - } if (config == null) { config = new GpgConfig(repo.getConfig()); } @@ -239,10 +196,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } catch (IOException e) { throw new JGitInternalException( JGitText.get().signatureVerificationError, e); - } finally { - if (ownVerifier) { - verifier.clear(); - } } return result; } @@ -258,8 +211,8 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR } if (type == Constants.OBJ_COMMIT || type == Constants.OBJ_TAG) { try { - GpgSignatureVerifier.SignatureVerification verification = verifier - .verifySignature(object, config); + SignatureVerification verification = SignatureVerifiers + .verify(repo, config, object); if (verification == null) { // Not signed return null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 8a5f2b2b30..310962b967 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -606,7 +606,6 @@ public class JGitText extends TranslationBundle { /***/ public String oldIdMustNotBeNull; /***/ public String onlyOneFetchSupported; /***/ public String onlyOneOperationCallPerConnectionIsSupported; - /***/ public String onlyOpenPgpSupportedForSigning; /***/ public String openFilesMustBeAtLeast1; /***/ public String openingConnection; /***/ public String operationCanceled; @@ -752,6 +751,8 @@ public class JGitText extends TranslationBundle { /***/ public String shutdownCleanup; /***/ public String shutdownCleanupFailed; /***/ public String shutdownCleanupListenerFailed; + /***/ public String signatureServiceConflict; + /***/ public String signatureTypeUnknown; /***/ public String signatureVerificationError; /***/ public String signatureVerificationUnavailable; /***/ public String signedTagMessageNoLf; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractGpgSignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractGpgSignatureVerifier.java deleted file mode 100644 index 06a89dc535..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractGpgSignatureVerifier.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> 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; - -import java.io.IOException; -import java.util.Arrays; - -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.revwalk.RevTag; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * Provides a base implementation of - * {@link GpgSignatureVerifier#verifySignature(RevObject, GpgConfig)}. - * - * @since 6.9 - */ -public abstract class AbstractGpgSignatureVerifier - implements GpgSignatureVerifier { - - @Override - public SignatureVerification verifySignature(RevObject object, - GpgConfig config) throws IOException { - if (object instanceof RevCommit) { - RevCommit commit = (RevCommit) object; - byte[] signatureData = commit.getRawGpgSignature(); - if (signatureData == null) { - return null; - } - byte[] raw = commit.getRawBuffer(); - // Now remove the GPG signature - byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' }; - int start = RawParseUtils.headerStart(header, raw, 0); - if (start < 0) { - return null; - } - int end = RawParseUtils.nextLfSkippingSplitLines(raw, start); - // start is at the beginning of the header's content - start -= header.length + 1; - // end is on the terminating LF; we need to skip that, too - if (end < raw.length) { - end++; - } - byte[] data = new byte[raw.length - (end - start)]; - System.arraycopy(raw, 0, data, 0, start); - System.arraycopy(raw, end, data, start, raw.length - end); - return verify(config, data, signatureData); - } else if (object instanceof RevTag) { - RevTag tag = (RevTag) object; - byte[] signatureData = tag.getRawGpgSignature(); - if (signatureData == null) { - return null; - } - byte[] raw = tag.getRawBuffer(); - // The signature is just tacked onto the end of the message, which - // is last in the buffer. - byte[] data = Arrays.copyOfRange(raw, 0, - raw.length - signatureData.length); - return verify(config, data, signatureData); - } - return null; - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index b9c90bda30..8599bf7dbc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -553,6 +553,20 @@ public final class Constants { public static final String GPG_SIGNATURE_PREFIX = "-----BEGIN PGP SIGNATURE-----"; //$NON-NLS-1$ /** + * Prefix of a CMS signature (X.509, S/MIME). + * + * @since 7.0 + */ + public static final String CMS_SIGNATURE_PREFIX = "-----BEGIN SIGNED MESSAGE-----"; //$NON-NLS-1$ + + /** + * Prefix of an SSH signature. + * + * @since 7.0 + */ + public static final String SSH_SIGNATURE_PREFIX = "-----BEGIN SSH SIGNATURE-----"; //$NON-NLS-1$ + + /** * Create a new digest function for objects. * * @return a new digest object. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java index f5064df061..fb5c904215 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java @@ -62,27 +62,6 @@ public class GpgConfig { private final boolean forceAnnotated; /** - * Create a {@link GpgConfig} with the given parameters and default - * {@code true} for signing commits and {@code false} for tags. - * - * @param keySpec - * to use - * @param format - * to use - * @param gpgProgram - * to use - * @since 5.11 - */ - public GpgConfig(String keySpec, GpgFormat format, String gpgProgram) { - keyFormat = format; - signingKey = keySpec; - program = gpgProgram; - signCommits = true; - signAllTags = false; - forceAnnotated = false; - } - - /** * Create a new GPG config that reads the configuration from config. * * @param config @@ -97,10 +76,11 @@ public class GpgConfig { String exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION, keyFormat.toConfigValue(), ConfigConstants.CONFIG_KEY_PROGRAM); - if (exe == null) { + if (exe == null && GpgFormat.OPENPGP.equals(keyFormat)) { exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION, null, ConfigConstants.CONFIG_KEY_PROGRAM); } + program = exe; signCommits = config.getBoolean(ConfigConstants.CONFIG_COMMIT_SECTION, ConfigConstants.CONFIG_KEY_GPGSIGN, false); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java deleted file mode 100644 index 074f46567b..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> 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; - -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.UnsupportedSigningFormatException; -import org.eclipse.jgit.transport.CredentialsProvider; - -/** - * Creates GPG signatures for Git objects. - * - * @since 5.11 - */ -public interface GpgObjectSigner { - - /** - * Signs the specified object. - * - * <p> - * Implementors should obtain the payload for signing from the specified - * object via {@link ObjectBuilder#build()} and create a proper - * {@link GpgSignature}. The generated signature must be set on the - * specified {@code object} (see - * {@link ObjectBuilder#setGpgSignature(GpgSignature)}). - * </p> - * <p> - * Any existing signature on the object must be discarded prior obtaining - * the payload via {@link ObjectBuilder#build()}. - * </p> - * - * @param object - * the object to sign (must not be {@code null} and must be - * complete to allow proper calculation of payload) - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @param config - * GPG settings from the git config - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - * @throws UnsupportedSigningFormatException - * if a config is given and the wanted key format is not - * supported - */ - void signObject(@NonNull ObjectBuilder object, - @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider, GpgConfig config) - throws CanceledException, UnsupportedSigningFormatException; - - /** - * Indicates if a signing key is available for the specified committer - * and/or signing key. - * - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @param config - * GPG settings from the git config - * @return <code>true</code> if a signing key is available, - * <code>false</code> otherwise - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - * @throws UnsupportedSigningFormatException - * if a config is given and the wanted key format is not - * supported - */ - public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey, - @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider, GpgConfig config) - throws CanceledException, UnsupportedSigningFormatException; - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java deleted file mode 100644 index 91c9bab5a4..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> 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; - -import java.io.IOException; -import java.util.Date; - -import org.eclipse.jgit.annotations.NonNull; -import org.eclipse.jgit.annotations.Nullable; -import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.revwalk.RevObject; - -/** - * A {@code GpgSignatureVerifier} can verify GPG signatures on git commits and - * tags. - * - * @since 5.11 - */ -public interface GpgSignatureVerifier { - - /** - * Verifies the signature on a signed commit or tag. - * - * @param object - * to verify - * @param config - * the {@link GpgConfig} to use - * @return a {@link SignatureVerification} describing the outcome of the - * verification, or {@code null} if the object was not signed - * @throws IOException - * if an error occurs getting a public key - * @throws org.eclipse.jgit.api.errors.JGitInternalException - * if signature verification fails - */ - @Nullable - SignatureVerification verifySignature(@NonNull RevObject object, - @NonNull GpgConfig config) throws IOException; - - /** - * Verifies a given signature for given data. - * - * @param config - * the {@link GpgConfig} - * @param data - * the signature is for - * @param signatureData - * the ASCII-armored signature - * @return a {@link SignatureVerification} describing the outcome - * @throws IOException - * if the signature cannot be parsed - * @throws JGitInternalException - * if signature verification fails - * @since 6.9 - */ - default SignatureVerification verify(@NonNull GpgConfig config, byte[] data, - byte[] signatureData) throws IOException { - // Default implementation for backwards compatibility; override as - // appropriate - return verify(data, signatureData); - } - - /** - * Verifies a given signature for given data. - * - * @param data - * the signature is for - * @param signatureData - * the ASCII-armored signature - * @return a {@link SignatureVerification} describing the outcome - * @throws IOException - * if the signature cannot be parsed - * @throws JGitInternalException - * if signature verification fails - * @deprecated since 6.9, use {@link #verify(GpgConfig, byte[], byte[])} - * instead - */ - @Deprecated - public SignatureVerification verify(byte[] data, byte[] signatureData) - throws IOException; - - /** - * Retrieves the name of this verifier. This should be a short string - * identifying the engine that verified the signature, like "gpg" if GPG is - * used, or "bc" for a BouncyCastle implementation. - * - * @return the name - */ - @NonNull - String getName(); - - /** - * A {@link GpgSignatureVerifier} may cache public keys to speed up - * verifying signatures on multiple objects. This clears this cache, if any. - */ - void clear(); - - /** - * A {@code SignatureVerification} returns data about a (positively or - * negatively) verified signature. - */ - interface SignatureVerification { - - // Data about the signature. - - @NonNull - Date getCreationDate(); - - // Data from the signature used to find a public key. - - /** - * Obtains the signer as stored in the signature, if known. - * - * @return the signer, or {@code null} if unknown - */ - String getSigner(); - - /** - * Obtains the short or long fingerprint of the public key as stored in - * the signature, if known. - * - * @return the fingerprint, or {@code null} if unknown - */ - String getKeyFingerprint(); - - // Some information about the found public key. - - /** - * Obtains the OpenPGP user ID associated with the key. - * - * @return the user id, or {@code null} if unknown - */ - String getKeyUser(); - - /** - * Tells whether the public key used for this signature verification was - * expired when the signature was created. - * - * @return {@code true} if the key was expired already, {@code false} - * otherwise - */ - boolean isExpired(); - - /** - * Obtains the trust level of the public key used to verify the - * signature. - * - * @return the trust level - */ - @NonNull - TrustLevel getTrustLevel(); - - // The verification result. - - /** - * Tells whether the signature verification was successful. - * - * @return {@code true} if the signature was verified successfully; - * {@code false} if not. - */ - boolean getVerified(); - - /** - * Obtains a human-readable message giving additional information about - * the outcome of the verification. - * - * @return the message, or {@code null} if none set. - */ - String getMessage(); - } - - /** - * The owner's trust in a public key. - */ - enum TrustLevel { - UNKNOWN, NEVER, MARGINAL, FULL, ULTIMATE - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java deleted file mode 100644 index 59775c475b..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> 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; - -import java.util.Iterator; -import java.util.ServiceConfigurationError; -import java.util.ServiceLoader; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@code GpgSignatureVerifierFactory} creates {@link GpgSignatureVerifier} instances. - * - * @since 5.11 - */ -public abstract class GpgSignatureVerifierFactory { - - private static final Logger LOG = LoggerFactory - .getLogger(GpgSignatureVerifierFactory.class); - - private static class DefaultFactory { - - private static volatile GpgSignatureVerifierFactory defaultFactory = loadDefault(); - - private static GpgSignatureVerifierFactory loadDefault() { - try { - ServiceLoader<GpgSignatureVerifierFactory> loader = ServiceLoader - .load(GpgSignatureVerifierFactory.class); - Iterator<GpgSignatureVerifierFactory> iter = loader.iterator(); - if (iter.hasNext()) { - return iter.next(); - } - } catch (ServiceConfigurationError e) { - LOG.error(e.getMessage(), e); - } - return null; - } - - private DefaultFactory() { - // No instantiation - } - - public static GpgSignatureVerifierFactory getDefault() { - return defaultFactory; - } - - /** - * Sets the default factory. - * - * @param factory - * the new default factory - */ - public static void setDefault(GpgSignatureVerifierFactory factory) { - defaultFactory = factory; - } - } - - /** - * Retrieves the default factory. - * - * @return the default factory or {@code null} if none set - */ - public static GpgSignatureVerifierFactory getDefault() { - return DefaultFactory.getDefault(); - } - - /** - * Sets the default factory. - * - * @param factory - * the new default factory - */ - public static void setDefault(GpgSignatureVerifierFactory factory) { - DefaultFactory.setDefault(factory); - } - - /** - * Creates a new {@link GpgSignatureVerifier}. - * - * @return the new {@link GpgSignatureVerifier} - */ - public abstract GpgSignatureVerifier getVerifier(); - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java deleted file mode 100644 index b25a61b506..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2018, 2022 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; - -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.transport.CredentialsProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Creates GPG signatures for Git objects. - * - * @since 5.3 - */ -public abstract class GpgSigner { - - private static final Logger LOG = LoggerFactory.getLogger(GpgSigner.class); - - private static class DefaultSigner { - - private static volatile GpgSigner defaultSigner = loadGpgSigner(); - - 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; - } - - private DefaultSigner() { - // No instantiation - } - - public static GpgSigner getDefault() { - return defaultSigner; - } - - public static void setDefault(GpgSigner signer) { - defaultSigner = signer; - } - } - - /** - * Get the default signer, or <code>null</code>. - * - * @return the default signer, or <code>null</code>. - */ - public static GpgSigner getDefault() { - return DefaultSigner.getDefault(); - } - - /** - * Set the default signer. - * - * @param signer - * the new default signer, may be <code>null</code> to select no - * default. - */ - public static void setDefault(GpgSigner signer) { - DefaultSigner.setDefault(signer); - } - - /** - * Signs the specified commit. - * - * <p> - * Implementors should obtain the payload for signing from the specified - * commit via {@link CommitBuilder#build()} and create a proper - * {@link GpgSignature}. The generated signature must be set on the - * specified {@code commit} (see - * {@link CommitBuilder#setGpgSignature(GpgSignature)}). - * </p> - * <p> - * Any existing signature on the commit must be discarded prior obtaining - * the payload via {@link CommitBuilder#build()}. - * </p> - * - * @param commit - * the commit to sign (must not be <code>null</code> and must be - * complete to allow proper calculation of payload) - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - */ - public abstract void sign(@NonNull CommitBuilder commit, - @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider) throws CanceledException; - - /** - * Indicates if a signing key is available for the specified committer - * and/or signing key. - * - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @return <code>true</code> if a signing key is available, - * <code>false</code> otherwise - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - */ - public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey, - @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider) throws CanceledException; - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java new file mode 100644 index 0000000000..2ce2708cb9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> 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; + +import java.io.IOException; +import java.util.Date; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.JGitInternalException; + +/** + * A {@code SignatureVerifier} can verify signatures on git commits and tags. + * + * @since 7.0 + */ +public interface SignatureVerifier { + + /** + * Verifies a given signature for given data. + * + * @param repository + * the {@link Repository} the data comes from. + * @param config + * the {@link GpgConfig} + * @param data + * the signature is for + * @param signatureData + * the ASCII-armored signature + * @return a {@link SignatureVerification} describing the outcome + * @throws IOException + * if the signature cannot be parsed + * @throws JGitInternalException + * if signature verification fails + */ + SignatureVerification verify(@NonNull Repository repository, + @NonNull GpgConfig config, byte[] data, byte[] signatureData) + throws IOException; + + /** + * Retrieves the name of this verifier. This should be a short string + * identifying the engine that verified the signature, like "gpg" if GPG is + * used, or "bc" for a BouncyCastle implementation. + * + * @return the name + */ + @NonNull + String getName(); + + /** + * A {@link SignatureVerifier} may cache public keys to speed up + * verifying signatures on multiple objects. This clears this cache, if any. + */ + void clear(); + + /** + * A {@code SignatureVerification} returns data about a (positively or + * negatively) verified signature. + * + * @param verifierName + * the name of the verifier that created this verification result + * @param creationDate + * date and time the signature was created + * @param signer + * the signer as stored in the signature, or {@code null} if + * unknown + * @param keyFingerprint + * fingerprint of the public key, or {@code null} if unknown + * @param keyUser + * user associated with the key, or {@code null} if unknown + * @param verified + * whether the signature verification was successful + * @param expired + * whether the public key used for this signature verification + * was expired when the signature was created + * @param trustLevel + * the trust level of the public key used to verify the signature + * @param message + * human-readable message giving additional information about the + * outcome of the verification, possibly {@code null} + */ + record SignatureVerification( + String verifierName, + Date creationDate, + String signer, + String keyFingerprint, + String keyUser, + boolean verified, + boolean expired, + @NonNull TrustLevel trustLevel, + String message) { + } + + /** + * The owner's trust in a public key. + */ + enum TrustLevel { + UNKNOWN, NEVER, MARGINAL, FULL, ULTIMATE + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java new file mode 100644 index 0000000000..7844aba3bd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> 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; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * A factory for {@link SignatureVerifier}s. + * + * @since 7.0 + */ +public interface SignatureVerifierFactory { + + /** + * Tells what kind of {@link SignatureVerifier} this factory creates. + * + * @return the {@link GpgConfig.GpgFormat} of the signer + */ + @NonNull + GpgConfig.GpgFormat getType(); + + /** + * Creates a new instance of a {@link SignatureVerifier} that can produce + * signatures of type {@link #getType()}. + * + * @return a new {@link SignatureVerifier} + */ + @NonNull + SignatureVerifier create(); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java new file mode 100644 index 0000000000..01c8422b66 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> 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; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.util.RawParseUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages the available signers. + * + * @since 7.0 + */ +public final class SignatureVerifiers { + + private static final Logger LOG = LoggerFactory.getLogger(SignatureVerifiers.class); + + private static final byte[] PGP_PREFIX = Constants.GPG_SIGNATURE_PREFIX + .getBytes(StandardCharsets.US_ASCII); + + private static final byte[] X509_PREFIX = Constants.CMS_SIGNATURE_PREFIX + .getBytes(StandardCharsets.US_ASCII); + + private static final byte[] SSH_PREFIX = Constants.SSH_SIGNATURE_PREFIX + .getBytes(StandardCharsets.US_ASCII); + + private static final Map<GpgConfig.GpgFormat, SignatureVerifierFactory> FACTORIES = loadSignatureVerifiers(); + + private static final Map<GpgConfig.GpgFormat, SignatureVerifier> VERIFIERS = new ConcurrentHashMap<>(); + + private static Map<GpgConfig.GpgFormat, SignatureVerifierFactory> loadSignatureVerifiers() { + Map<GpgConfig.GpgFormat, SignatureVerifierFactory> result = new EnumMap<>( + GpgConfig.GpgFormat.class); + try { + for (SignatureVerifierFactory factory : ServiceLoader + .load(SignatureVerifierFactory.class)) { + GpgConfig.GpgFormat format = factory.getType(); + SignatureVerifierFactory existing = result.get(format); + if (existing != null) { + LOG.warn("{}", //$NON-NLS-1$ + MessageFormat.format( + JGitText.get().signatureServiceConflict, + "SignatureVerifierFactory", format, //$NON-NLS-1$ + existing.getClass().getCanonicalName(), + factory.getClass().getCanonicalName())); + } else { + result.put(format, factory); + } + } + } catch (ServiceConfigurationError e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + private SignatureVerifiers() { + // No instantiation + } + + /** + * Retrieves a {@link Signer} that can produce signatures of the given type + * {@code format}. + * + * @param format + * {@link GpgConfig.GpgFormat} the signer must support + * @return a {@link Signer}, or {@code null} if none is available + */ + public static SignatureVerifier get(@NonNull GpgConfig.GpgFormat format) { + return VERIFIERS.computeIfAbsent(format, f -> { + SignatureVerifierFactory factory = FACTORIES.get(format); + if (factory == null) { + return null; + } + return factory.create(); + }); + } + + /** + * Sets a specific signature verifier to use for a specific signature type. + * + * @param format + * signature type to set the {@code verifier} for + * @param verifier + * the {@link SignatureVerifier} to use for signatures of type + * {@code format}; if {@code null}, a default implementation, if + * available, may be used. + */ + public static void set(@NonNull GpgConfig.GpgFormat format, + SignatureVerifier verifier) { + SignatureVerifier previous; + if (verifier == null) { + previous = VERIFIERS.remove(format); + } else { + previous = VERIFIERS.put(format, verifier); + } + if (previous != null) { + previous.clear(); + } + } + + /** + * Verifies the signature on a signed commit or tag. + * + * @param repository + * the {@link Repository} the object is from + * @param config + * the {@link GpgConfig} to use + * @param object + * to verify + * @return a {@link SignatureVerifier.SignatureVerification} describing the + * outcome of the verification, or {@code null} if the object does + * not have a signature of a known type + * @throws IOException + * if an error occurs getting a public key + * @throws org.eclipse.jgit.api.errors.JGitInternalException + * if signature verification fails + */ + @Nullable + public static SignatureVerifier.SignatureVerification verify( + @NonNull Repository repository, @NonNull GpgConfig config, + @NonNull RevObject object) throws IOException { + if (object instanceof RevCommit) { + RevCommit commit = (RevCommit) object; + byte[] signatureData = commit.getRawGpgSignature(); + if (signatureData == null) { + return null; + } + byte[] raw = commit.getRawBuffer(); + // Now remove the GPG signature + byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' }; + int start = RawParseUtils.headerStart(header, raw, 0); + if (start < 0) { + return null; + } + int end = RawParseUtils.nextLfSkippingSplitLines(raw, start); + // start is at the beginning of the header's content + start -= header.length + 1; + // end is on the terminating LF; we need to skip that, too + if (end < raw.length) { + end++; + } + byte[] data = new byte[raw.length - (end - start)]; + System.arraycopy(raw, 0, data, 0, start); + System.arraycopy(raw, end, data, start, raw.length - end); + return verify(repository, config, data, signatureData); + } else if (object instanceof RevTag) { + RevTag tag = (RevTag) object; + byte[] signatureData = tag.getRawGpgSignature(); + if (signatureData == null) { + return null; + } + byte[] raw = tag.getRawBuffer(); + // The signature is just tacked onto the end of the message, which + // is last in the buffer. + byte[] data = Arrays.copyOfRange(raw, 0, + raw.length - signatureData.length); + return verify(repository, config, data, signatureData); + } + return null; + } + + /** + * Verifies a given signature for some give data. + * + * @param repository + * the {@link Repository} the object is from + * @param config + * the {@link GpgConfig} to use + * @param data + * to verify the signature of + * @param signature + * the given signature of the {@code data} + * @return a {@link SignatureVerifier.SignatureVerification} describing the + * outcome of the verification, or {@code null} if the signature + * type is unknown + * @throws IOException + * if an error occurs getting a public key + * @throws org.eclipse.jgit.api.errors.JGitInternalException + * if signature verification fails + */ + @Nullable + public static SignatureVerifier.SignatureVerification verify( + @NonNull Repository repository, @NonNull GpgConfig config, + byte[] data, byte[] signature) throws IOException { + GpgConfig.GpgFormat format = getFormat(signature); + if (format == null) { + return null; + } + SignatureVerifier verifier = get(format); + if (verifier == null) { + return null; + } + return verifier.verify(repository, config, data, signature); + } + + /** + * Determines the type of a given signature. + * + * @param signature + * to get the type of + * @return the signature type, or {@code null} if unknown + */ + @Nullable + public static GpgConfig.GpgFormat getFormat(byte[] signature) { + if (RawParseUtils.match(signature, 0, PGP_PREFIX) > 0) { + return GpgConfig.GpgFormat.OPENPGP; + } + if (RawParseUtils.match(signature, 0, X509_PREFIX) > 0) { + return GpgConfig.GpgFormat.X509; + } + if (RawParseUtils.match(signature, 0, SSH_PREFIX) > 0) { + return GpgConfig.GpgFormat.SSH; + } + return null; + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java new file mode 100644 index 0000000000..3bb7464d08 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> 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; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import org.eclipse.jgit.annotations.NonNull; +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.transport.CredentialsProvider; + +/** + * Creates signatures for Git objects. + * + * @since 7.0 + */ +public interface Signer { + + /** + * Signs the specified object. + * + * <p> + * Implementors should obtain the payload for signing from the specified + * object via {@link ObjectBuilder#build()} and create a proper + * {@link GpgSignature}. The generated signature is set on the specified + * {@code object} (see {@link ObjectBuilder#setGpgSignature(GpgSignature)}). + * </p> + * <p> + * Any existing signature on the object must be discarded prior obtaining + * the payload via {@link ObjectBuilder#build()}. + * </p> + * + * @param repository + * {@link Repository} the object belongs to + * @param config + * GPG settings from the git config + * @param object + * the object to sign (must not be {@code null} and must be + * complete to allow proper calculation of payload) + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param signingKey + * if non-{@code null} overrides the signing key from the config + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + * @throws IOException + * if an I/O error occurs + * @throws UnsupportedSigningFormatException + * if a config is given and the wanted key format is not + * supported + */ + default void signObject(@NonNull Repository repository, + @NonNull GpgConfig config, @NonNull ObjectBuilder object, + @NonNull PersonIdent committer, String signingKey, + CredentialsProvider credentialsProvider) + throws CanceledException, IOException, + UnsupportedSigningFormatException { + try { + object.setGpgSignature(sign(repository, config, object.build(), + committer, signingKey, credentialsProvider)); + } catch (UnsupportedEncodingException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + + /** + * Signs arbitrary data. + * + * @param repository + * {@link Repository} the signature is created in + * @param config + * GPG settings from the git config + * @param data + * the data to sign + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param signingKey + * if non-{@code null} overrides the signing key from the config + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @return the signature for {@code data} + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + * @throws IOException + * if an I/O error occurs + * @throws UnsupportedSigningFormatException + * if a config is given and the wanted key format is not + * supported + */ + GpgSignature sign(@NonNull Repository repository, @NonNull GpgConfig config, + byte[] data, @NonNull PersonIdent committer, String signingKey, + CredentialsProvider credentialsProvider) throws CanceledException, + IOException, UnsupportedSigningFormatException; + + /** + * Indicates if a signing key is available for the specified committer + * and/or signing key. + * + * @param repository + * the current {@link Repository} + * @param config + * GPG settings from the git config + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param signingKey + * if non-{@code null} overrides the signing key from the config + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @return {@code true} if a signing key is available, {@code false} + * otherwise + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + */ + boolean canLocateSigningKey(@NonNull Repository repository, + @NonNull GpgConfig config, @NonNull PersonIdent committer, + String signingKey, CredentialsProvider credentialsProvider) + throws CanceledException; + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java new file mode 100644 index 0000000000..125d25e3b7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> 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; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * A factory for {@link Signer}s. + * + * @since 7.0 + */ +public interface SignerFactory { + + /** + * Tells what kind of {@link Signer} this factory creates. + * + * @return the {@link GpgConfig.GpgFormat} of the signer + */ + @NonNull + GpgConfig.GpgFormat getType(); + + /** + * Creates a new instance of a {@link Signer} that can produce signatures of + * type {@link #getType()}. + * + * @return a new {@link Signer} + */ + @NonNull + Signer create(); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java new file mode 100644 index 0000000000..7771b07841 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> 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; + +import java.text.MessageFormat; +import java.util.EnumMap; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.JGitText; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages the available signers. + * + * @since 7.0 + */ +public final class Signers { + + private static final Logger LOG = LoggerFactory.getLogger(Signers.class); + + private static final Map<GpgConfig.GpgFormat, SignerFactory> SIGNER_FACTORIES = loadSigners(); + + private static final Map<GpgConfig.GpgFormat, Signer> SIGNERS = new ConcurrentHashMap<>(); + + private static Map<GpgConfig.GpgFormat, SignerFactory> loadSigners() { + Map<GpgConfig.GpgFormat, SignerFactory> result = new EnumMap<>( + GpgConfig.GpgFormat.class); + try { + for (SignerFactory factory : ServiceLoader + .load(SignerFactory.class)) { + GpgConfig.GpgFormat format = factory.getType(); + SignerFactory existing = result.get(format); + if (existing != null) { + LOG.warn("{}", //$NON-NLS-1$ + MessageFormat.format( + JGitText.get().signatureServiceConflict, + "SignerFactory", format, //$NON-NLS-1$ + existing.getClass().getCanonicalName(), + factory.getClass().getCanonicalName())); + } else { + result.put(format, factory); + } + } + } catch (ServiceConfigurationError e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + private Signers() { + // No instantiation + } + + /** + * Retrieves a {@link Signer} that can produce signatures of the given type + * {@code format}. + * + * @param format + * {@link GpgConfig.GpgFormat} the signer must support + * @return a {@link Signer}, or {@code null} if none is available + */ + public static Signer get(@NonNull GpgConfig.GpgFormat format) { + return SIGNERS.computeIfAbsent(format, f -> { + SignerFactory factory = SIGNER_FACTORIES.get(format); + if (factory == null) { + return null; + } + return factory.create(); + }); + } + + /** + * Sets a specific signer to use for a specific signature type. + * + * @param format + * signature type to set the {@code signer} for + * @param signer + * the {@link Signer} to use for signatures of type + * {@code format}; if {@code null}, a default implementation, if + * available, may be used. + */ + public static void set(@NonNull GpgConfig.GpgFormat format, Signer signer) { + if (signer == null) { + SIGNERS.remove(format); + } else { + SIGNERS.put(format, signer); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java index 5b50c2afd7..0737a78085 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java @@ -13,7 +13,6 @@ package org.eclipse.jgit.revwalk; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.eclipse.jgit.lib.Constants.GPG_SIGNATURE_PREFIX; import java.io.IOException; import java.nio.charset.Charset; @@ -39,8 +38,17 @@ import org.eclipse.jgit.util.StringUtils; */ public class RevTag extends RevObject { - private static final byte[] hSignature = Constants - .encodeASCII(GPG_SIGNATURE_PREFIX); + private static final byte[] SIGNATURE_START = Constants + .encodeASCII("-----BEGIN"); //$NON-NLS-1$ + + private static final byte[] GPG_SIGNATURE_START = Constants + .encodeASCII(Constants.GPG_SIGNATURE_PREFIX); + + private static final byte[] CMS_SIGNATURE_START = Constants + .encodeASCII(Constants.CMS_SIGNATURE_PREFIX); + + private static final byte[] SSH_SIGNATURE_START = Constants + .encodeASCII(Constants.SSH_SIGNATURE_PREFIX); /** * Parse an annotated tag from its canonical format. @@ -209,20 +217,27 @@ public class RevTag extends RevObject { return msgB; } // Find the last signature start and return the rest - int start = nextStart(hSignature, raw, msgB); + int start = nextStart(SIGNATURE_START, raw, msgB); if (start < 0) { return start; } int next = RawParseUtils.nextLF(raw, start); while (next < raw.length) { - int newStart = nextStart(hSignature, raw, next); + int newStart = nextStart(SIGNATURE_START, raw, next); if (newStart < 0) { break; } start = newStart; next = RawParseUtils.nextLF(raw, start); } - return start; + // SIGNATURE_START is just a prefix. Check that it is one of the known + // full signature start tags. + if (RawParseUtils.match(raw, start, GPG_SIGNATURE_START) > 0 + || RawParseUtils.match(raw, start, CMS_SIGNATURE_START) > 0 + || RawParseUtils.match(raw, start, SSH_SIGNATURE_START) > 0) { + return start; + } + return -1; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java index cf06172c17..90524db20a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java @@ -13,8 +13,8 @@ import java.text.MessageFormat; import java.util.Locale; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; -import org.eclipse.jgit.lib.GpgSignatureVerifier.TrustLevel; +import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.SignatureVerifier.TrustLevel; import org.eclipse.jgit.lib.PersonIdent; /** @@ -39,29 +39,31 @@ public final class SignatureUtils { * to use for dates * @return a textual representation of the {@link SignatureVerification}, * using LF as line separator + * + * @since 7.0 */ public static String toString(SignatureVerification verification, PersonIdent creator, GitDateFormatter formatter) { StringBuilder result = new StringBuilder(); // Use the creator's timezone for the signature date PersonIdent dateId = new PersonIdent(creator, - verification.getCreationDate()); + verification.creationDate()); result.append(MessageFormat.format(JGitText.get().verifySignatureMade, formatter.formatDate(dateId))); result.append('\n'); result.append(MessageFormat.format( JGitText.get().verifySignatureKey, - verification.getKeyFingerprint().toUpperCase(Locale.ROOT))); + verification.keyFingerprint().toUpperCase(Locale.ROOT))); result.append('\n'); - if (!StringUtils.isEmptyOrNull(verification.getSigner())) { + if (!StringUtils.isEmptyOrNull(verification.signer())) { result.append( MessageFormat.format(JGitText.get().verifySignatureIssuer, - verification.getSigner())); + verification.signer())); result.append('\n'); } String msg; - if (verification.getVerified()) { - if (verification.isExpired()) { + if (verification.verified()) { + if (verification.expired()) { msg = JGitText.get().verifySignatureExpired; } else { msg = JGitText.get().verifySignatureGood; @@ -69,14 +71,14 @@ public final class SignatureUtils { } else { msg = JGitText.get().verifySignatureBad; } - result.append(MessageFormat.format(msg, verification.getKeyUser())); - if (!TrustLevel.UNKNOWN.equals(verification.getTrustLevel())) { + result.append(MessageFormat.format(msg, verification.keyUser())); + if (!TrustLevel.UNKNOWN.equals(verification.trustLevel())) { result.append(' ' + MessageFormat .format(JGitText.get().verifySignatureTrust, verification - .getTrustLevel().name().toLowerCase(Locale.ROOT))); + .trustLevel().name().toLowerCase(Locale.ROOT))); } result.append('\n'); - msg = verification.getMessage(); + msg = verification.message(); if (!StringUtils.isEmptyOrNull(msg)) { result.append(msg); result.append('\n'); |