Skip to content

Instantly share code, notes, and snippets.

@sdorra
Created July 22, 2020 05:02
Show Gist options
  • Save sdorra/b139ea8725b8aaaf98e9c07408cdbbc0 to your computer and use it in GitHub Desktop.
Save sdorra/b139ea8725b8aaaf98e9c07408cdbbc0 to your computer and use it in GitHub Desktop.
Sign and verify commits with JGit
import org.bouncycastle.bcpg.ArmoredInputStream;
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.PGPKeyPair;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.GpgSignature;
import org.eclipse.jgit.lib.GpgSigner;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.util.RawParseUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.Arrays;
import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat;
class JGitTest {
private static void registerBouncyCastleProviderIfNecessary() {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
@Test
void signAndVerify(@TempDir Path workdir) throws IOException, GitAPIException, PGPException, NoSuchProviderException, NoSuchAlgorithmException {
registerBouncyCastleProviderIfNecessary();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048);
KeyPair pair = keyPairGenerator.generateKeyPair();
String identity = "0xAWESOMExBOB";
PGPKeyPair keyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, pair, new Date());
ScmGpgSigner signer = new ScmGpgSigner(keyPair);
GpgSigner.setDefault(signer);
Path repositoryPath = workdir.resolve("repository");
try (Git git = Git.init().setDirectory(repositoryPath.toFile()).call()) {
Files.write(repositoryPath.resolve("README.md"), "# Hello".getBytes(StandardCharsets.UTF_8));
git.add().addFilepattern("README.md").call();
git.commit()
.setAuthor("Bob The Signer", "[email protected]")
.setMessage("Signed from Bob")
.setSign(true)
.setSigningKey(identity)
.call();
RevCommit commit = git.log().setMaxCount(1).call().iterator().next();
byte[] raw = commit.getRawBuffer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'};
final int start = RawParseUtils.headerStart(header, raw, 0);
final int end = RawParseUtils.headerEnd(raw, start);
byte[] headerPrefix = Arrays.copyOfRange(raw, 0, start - header.length - 1);
baos.write(headerPrefix);
byte[] headerSuffix = Arrays.copyOfRange(raw, end + 1, raw.length);
baos.write(headerSuffix);
System.out.println("-----BEGIN COMMIT-----");
System.out.println(baos);
System.out.println("-----END COMMIT-----");
byte[] signature = Arrays.copyOfRange(raw, start, end);
System.out.println(new String(signature, commit.getEncoding()));
ArmoredInputStream armoredInputStream = new ArmoredInputStream(new ByteArrayInputStream(signature));
PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(armoredInputStream, null);
PGPSignatureList list = (PGPSignatureList) pgpObjectFactory.nextObject();
for (PGPSignature s : list) {
PGPContentVerifierBuilderProvider provider = new JcaPGPContentVerifierBuilderProvider();
s.init(provider, keyPair.getPublicKey());
s.update(baos.toByteArray());
assertThat(s.verify()).isTrue();
}
}
}
private static class ScmGpgSigner extends GpgSigner {
private final PGPKeyPair keyPair;
ScmGpgSigner(PGPKeyPair keyPair) {
this.keyPair = keyPair;
}
@Override
public boolean canLocateSigningKey(@Nullable String gpgSigningKey, PersonIdent committer, CredentialsProvider credentialsProvider) {
return true;
}
@Override
public void sign(@NonNull CommitBuilder commit, @Nullable String gpgSigningKey,
@NonNull PersonIdent committer, CredentialsProvider credentialsProvider) {
try {
if (keyPair == null) {
throw new JGitInternalException(JGitText.get().unableToSignCommitNoSecretKey);
}
PGPPrivateKey privateKey = keyPair.getPrivateKey();
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
new JcaPGPContentSignerBuilder(
keyPair.getPublicKey().getAlgorithm(),
HashAlgorithmTags.SHA256).setProvider(BouncyCastleProvider.PROVIDER_NAME)
);
signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (BCPGOutputStream out = new BCPGOutputStream(new ArmoredOutputStream(buffer))) {
signatureGenerator.update(commit.build());
signatureGenerator.generate().encode(out);
}
commit.setGpgSignature(new GpgSignature(buffer.toByteArray()));
} catch (PGPException | IOException e) {
throw new JGitInternalException(e.getMessage(), e);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment