import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.GpgSignature;
import org.eclipse.jgit.lib.GpgSigner;
+import org.eclipse.jgit.lib.GpgObjectSigner;
+import org.eclipse.jgit.lib.ObjectBuilder;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.util.StringUtils;
/**
* GPG Signer using BouncyCastle library
*/
-public class BouncyCastleGpgSigner extends GpgSigner {
+public class BouncyCastleGpgSigner extends GpgSigner
+ implements GpgObjectSigner {
private static void registerBouncyCastleProviderIfNecessary() {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
public void sign(@NonNull CommitBuilder commit,
@Nullable String gpgSigningKey, @NonNull PersonIdent committer,
CredentialsProvider credentialsProvider) throws CanceledException {
+ signObject(commit, gpgSigningKey, committer, credentialsProvider);
+ }
+
+ @Override
+ public void signObject(@NonNull ObjectBuilder object,
+ @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
+ CredentialsProvider credentialsProvider) throws CanceledException {
try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt(
credentialsProvider)) {
BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (BCPGOutputStream out = new BCPGOutputStream(
new ArmoredOutputStream(buffer))) {
- signatureGenerator.update(commit.build());
+ signatureGenerator.update(object.build());
signatureGenerator.generate().encode(out);
}
- commit.setGpgSignature(new GpgSignature(buffer.toByteArray()));
+ object.setGpgSignature(new GpgSignature(buffer.toByteArray()));
} catch (PGPException | IOException | NoSuchAlgorithmException
| NoSuchProviderException | URISyntaxException e) {
throw new JGitInternalException(e.getMessage(), e);
/*
- * Copyright (C) 2018, Salesforce. and others
+ * Copyright (C) 2018, 2020 Salesforce. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
private void assertGpgSignatureStringOutcome(String signature,
String expectedOutcome) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
- CommitBuilder.writeGpgSignatureString(signature, out);
+ ObjectBuilder.writeMultiLineHeader(signature, out, true);
String formatted_signature = new String(out.toByteArray(), US_ASCII);
assertEquals(expectedOutcome, formatted_signature);
}
String signature = "Ü Ä";
IllegalArgumentException e = assertThrows(
IllegalArgumentException.class,
- () -> CommitBuilder.writeGpgSignatureString(signature,
- new ByteArrayOutputStream()));
+ () -> ObjectBuilder.writeMultiLineHeader(signature,
+ new ByteArrayOutputStream(), true));
String message = MessageFormat.format(JGitText.get().notASCIIString,
signature);
assertEquals(message, e.getMessage());
--- /dev/null
+/*
+ * 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 static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.junit.Test;
+
+public class TagBuilderTest {
+
+ // @formatter:off
+ private static final String SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" +
+ "Version: BCPG v1.60\n" +
+ "\n" +
+ "iQEcBAABCAAGBQJb9cVhAAoJEKX+6Axg/6TZeFsH/0CY0WX/z7U8+7S5giFX4wH4\n" +
+ "opvBwqyt6OX8lgNwTwBGHFNt8LdmDCCmKoq/XwkNi3ARVjLhe3gBcKXNoavvPk2Z\n" +
+ "gIg5ChevGkU4afWCOMLVEYnkCBGw2+86XhrK1P7gTHEk1Rd+Yv1ZRDJBY+fFO7yz\n" +
+ "uSBuF5RpEY2sJiIvp27Gub/rY3B5NTR/feO/z+b9oiP/fMUhpRwG5KuWUsn9NPjw\n" +
+ "3tvbgawYpU/2UnS+xnavMY4t2fjRYjsoxndPLb2MUX8X7vC7FgWLBlmI/rquLZVM\n" +
+ "IQEKkjnA+lhejjK1rv+ulq4kGZJFKGYWYYhRDwFg5PTkzhudhN2SGUq5Wxq1Eg4=\n" +
+ "=b9OI\n" +
+ "-----END PGP SIGNATURE-----";
+
+ // @formatter:on
+
+ private static final String TAGGER_LINE = "A U. Thor <a_u_thor@example.com> 1218123387 +0700";
+
+ private static final PersonIdent TAGGER = RawParseUtils
+ .parsePersonIdent(TAGGER_LINE);
+
+ @Test
+ public void testTagSimple() throws Exception {
+ TagBuilder t = new TagBuilder();
+ t.setTag("sometag");
+ t.setObjectId(ObjectId.zeroId(), Constants.OBJ_COMMIT);
+ t.setEncoding(US_ASCII);
+ t.setMessage("Short message only");
+ t.setTagger(TAGGER);
+ String tag = new String(t.build(), UTF_8);
+ String expected = "object 0000000000000000000000000000000000000000\n"
+ + "type commit\n" //
+ + "tag sometag\n" //
+ + "tagger " + TAGGER_LINE + '\n' //
+ + "encoding US-ASCII\n" //
+ + '\n' //
+ + "Short message only";
+ assertEquals(expected, tag);
+ }
+
+ @Test
+ public void testTagWithSignatureShortMessageEndsInLF() throws Exception {
+ TagBuilder t = new TagBuilder();
+ t.setTag("sometag");
+ t.setObjectId(ObjectId.zeroId(), Constants.OBJ_COMMIT);
+ t.setEncoding(US_ASCII);
+ t.setMessage("Short message only\n");
+ t.setTagger(TAGGER);
+ t.setGpgSignature(new GpgSignature(SIGNATURE.getBytes(US_ASCII)));
+ String tag = new String(t.build(), UTF_8);
+ String expected = "object 0000000000000000000000000000000000000000\n"
+ + "type commit\n" //
+ + "tag sometag\n" //
+ + "tagger " + TAGGER_LINE + '\n' //
+ + "encoding US-ASCII\n" //
+ + '\n' //
+ + "Short message only\n" //
+ + SIGNATURE + '\n';
+ assertEquals(expected, tag);
+ }
+
+ @Test
+ public void testTagWithSignatureMessageNoLF() {
+ TagBuilder t = new TagBuilder();
+ t.setTag("sometag");
+ t.setObjectId(ObjectId.zeroId(), Constants.OBJ_COMMIT);
+ t.setEncoding(US_ASCII);
+ t.setMessage("A message\n\nthat does not end in LF");
+ t.setTagger(TAGGER);
+ t.setGpgSignature(new GpgSignature(SIGNATURE.getBytes(US_ASCII)));
+ Throwable ex = assertThrows(Throwable.class, t::build);
+ assertEquals(JGitText.get().signedTagMessageNoLf, ex.getMessage());
+ }
+
+ @Test
+ public void testTagWithSignatureNoParagraphsMessage() throws Exception {
+ TagBuilder t = new TagBuilder();
+ t.setTag("sometag");
+ t.setObjectId(ObjectId.zeroId(), Constants.OBJ_COMMIT);
+ t.setEncoding(US_ASCII);
+ t.setMessage("A strange\ntag message\n");
+ t.setTagger(TAGGER);
+ t.setGpgSignature(new GpgSignature(SIGNATURE.getBytes(US_ASCII)));
+ String tag = new String(t.build(), UTF_8);
+ String expected = "object 0000000000000000000000000000000000000000\n"
+ + "type commit\n" //
+ + "tag sometag\n" //
+ + "tagger " + TAGGER_LINE + '\n' //
+ + "encoding US-ASCII\n" //
+ + '\n' //
+ + "A strange\ntag message\n" //
+ + SIGNATURE + '\n';
+ assertEquals(expected, tag);
+ }
+
+ @Test
+ public void testTagWithSignatureLongMessage() throws Exception {
+ TagBuilder t = new TagBuilder();
+ t.setTag("sometag");
+ t.setObjectId(ObjectId.zeroId(), Constants.OBJ_COMMIT);
+ t.setMessage("Short message\n\nFollowed by explanations.\n");
+ t.setTagger(TAGGER);
+ t.setGpgSignature(new GpgSignature(SIGNATURE.getBytes(US_ASCII)));
+ String tag = new String(t.build(), UTF_8);
+ String expected = "object 0000000000000000000000000000000000000000\n"
+ + "type commit\n" //
+ + "tag sometag\n" //
+ + "tagger " + TAGGER_LINE + '\n' //
+ + '\n' //
+ + "Short message\n\nFollowed by explanations.\n" //
+ + SIGNATURE + '\n';
+ assertEquals(expected, tag);
+ }
+
+ @Test
+ public void testTagWithSignatureEmptyMessage() throws Exception {
+ TagBuilder t = new TagBuilder();
+ t.setTag("sometag");
+ t.setObjectId(ObjectId.zeroId(), Constants.OBJ_COMMIT);
+ t.setTagger(TAGGER);
+ t.setMessage("");
+ String emptyMsg = new String(t.build(), UTF_8);
+ t.setGpgSignature(new GpgSignature(SIGNATURE.getBytes(US_ASCII)));
+ String tag = new String(t.build(), UTF_8);
+ String expected = "object 0000000000000000000000000000000000000000\n"
+ + "type commit\n" //
+ + "tag sometag\n" //
+ + "tagger " + TAGGER_LINE + '\n' //
+ + '\n';
+ assertEquals(expected, emptyMsg);
+ assertEquals(expected + SIGNATURE + '\n', tag);
+ }
+
+ @Test
+ public void testTagWithSignatureOnly() throws Exception {
+ TagBuilder t = new TagBuilder();
+ t.setTag("sometag");
+ t.setObjectId(ObjectId.zeroId(), Constants.OBJ_COMMIT);
+ t.setTagger(TAGGER);
+ String emptyMsg = new String(t.build(), UTF_8);
+ t.setGpgSignature(new GpgSignature(SIGNATURE.getBytes(US_ASCII)));
+ String tag = new String(t.build(), UTF_8);
+ String expected = "object 0000000000000000000000000000000000000000\n"
+ + "type commit\n" //
+ + "tag sometag\n" //
+ + "tagger " + TAGGER_LINE + '\n' //
+ + '\n';
+ assertEquals(expected, emptyMsg);
+ assertEquals(expected + SIGNATURE + '\n', tag);
+ }
+
+}
/*
- * Copyright (C) 2008-2010, Google Inc. and others
+ * Copyright (C) 2008, 2020, Google Inc. 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
package org.eclipse.jgit.revwalk;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.junit.RepositoryTestCase;
assertNotNull(c.getTagName());
assertEquals(name, c.getTagName());
assertEquals("", c.getFullMessage());
+ assertNull(c.getRawGpgSignature());
final PersonIdent cTagger = c.getTaggerIdent();
assertNotNull(cTagger);
public void testParseOldStyleNoTagger() throws Exception {
final ObjectId treeId = id("9788669ad918b6fcce64af8882fc9a81cb6aba67");
final String name = "v1.2.3.4.5";
- final String message = "test\n" //
- + "\n" //
- + "-----BEGIN PGP SIGNATURE-----\n" //
+ final String fakeSignature = "-----BEGIN PGP SIGNATURE-----\n" //
+ "Version: GnuPG v1.4.1 (GNU/Linux)\n" //
+ "\n" //
+ "iD8DBQBC0b9oF3Y\n" //
- + "-----END PGP SIGNATURE------n";
+ + "-----END PGP SIGNATURE-----";
+ final String message = "test\n\n" + fakeSignature + '\n';
final StringBuilder body = new StringBuilder();
assertEquals(name, c.getTagName());
assertEquals("test", c.getShortMessage());
assertEquals(message, c.getFullMessage());
+ assertEquals(fakeSignature + '\n',
+ new String(c.getRawGpgSignature(), US_ASCII));
assertNull(c.getTaggerIdent());
}
assertEquals("message\n", t.getFullMessage());
}
+ @Test
+ public void testParse_gpgSignature() throws Exception {
+ final String signature = "-----BEGIN PGP SIGNATURE-----\n\n"
+ + "wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n"
+ + "U24B1Tlq/DWvce2buaxmbNQngKZ0pv2s8VMc11916WfTIC9EKvioatmpjduWvhqj\n"
+ + "znQTFyiMor30pyYsfrqFuQZvqBW01o8GEWqLg8zjf9Rf0R3LlOEw86aT8CdHRlm6\n"
+ + "wlb22xb8qoX4RB+LYfz7MhK5F+yLOPXZdJnAVbuyoMGRnDpwdzjL5Hj671+XJxN5\n"
+ + "SasRdhxkkfw/ZnHxaKEc4juMz8Nziz27elRwhOQqlTYoXNJnsV//wy5Losd7aKi1\n"
+ + "xXXyUpndEOmT0CIcKHrN/kbYoVL28OJaxoBuva3WYQaRrzEe3X02NMxZe9gkSqA=\n"
+ + "=TClh\n" + "-----END PGP SIGNATURE-----";
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
+ .getBytes(UTF_8));
+ b.write("type tree\n".getBytes(UTF_8));
+ b.write("tag v1.0\n".getBytes(UTF_8));
+ b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+ b.write('\n');
+ b.write("message\n\n".getBytes(UTF_8));
+ b.write(signature.getBytes(US_ASCII));
+ b.write('\n');
+
+ RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+ try (RevWalk rw = new RevWalk(db)) {
+ t.parseCanonical(rw, b.toByteArray());
+ }
+
+ assertEquals("t", t.getTaggerIdent().getName());
+ assertEquals("message", t.getShortMessage());
+ assertEquals("message\n\n" + signature + '\n', t.getFullMessage());
+ String gpgSig = new String(t.getRawGpgSignature(), UTF_8);
+ assertEquals(signature + '\n', gpgSig);
+ }
+
+ @Test
+ public void testParse_gpgSignature2() throws Exception {
+ final String signature = "-----BEGIN PGP SIGNATURE-----\n\n"
+ + "wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n"
+ + "U24B1Tlq/DWvce2buaxmbNQngKZ0pv2s8VMc11916WfTIC9EKvioatmpjduWvhqj\n"
+ + "znQTFyiMor30pyYsfrqFuQZvqBW01o8GEWqLg8zjf9Rf0R3LlOEw86aT8CdHRlm6\n"
+ + "wlb22xb8qoX4RB+LYfz7MhK5F+yLOPXZdJnAVbuyoMGRnDpwdzjL5Hj671+XJxN5\n"
+ + "SasRdhxkkfw/ZnHxaKEc4juMz8Nziz27elRwhOQqlTYoXNJnsV//wy5Losd7aKi1\n"
+ + "xXXyUpndEOmT0CIcKHrN/kbYoVL28OJaxoBuva3WYQaRrzEe3X02NMxZe9gkSqA=\n"
+ + "=TClh\n" + "-----END PGP SIGNATURE-----";
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
+ .getBytes(UTF_8));
+ b.write("type tree\n".getBytes(UTF_8));
+ b.write("tag v1.0\n".getBytes(UTF_8));
+ b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+ b.write('\n');
+ String message = "message\n\n" + signature.replace("xXXy", "aAAb")
+ + '\n';
+ b.write(message.getBytes(UTF_8));
+ b.write(signature.getBytes(US_ASCII));
+ b.write('\n');
+
+ RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+ try (RevWalk rw = new RevWalk(db)) {
+ t.parseCanonical(rw, b.toByteArray());
+ }
+
+ assertEquals("t", t.getTaggerIdent().getName());
+ assertEquals("message", t.getShortMessage());
+ assertEquals(message + signature + '\n', t.getFullMessage());
+ String gpgSig = new String(t.getRawGpgSignature(), UTF_8);
+ assertEquals(signature + '\n', gpgSig);
+ }
+
+ @Test
+ public void testParse_gpgSignature3() throws Exception {
+ final String signature = "-----BEGIN PGP SIGNATURE-----\n\n"
+ + "wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n"
+ + "U24B1Tlq/DWvce2buaxmbNQngKZ0pv2s8VMc11916WfTIC9EKvioatmpjduWvhqj\n"
+ + "znQTFyiMor30pyYsfrqFuQZvqBW01o8GEWqLg8zjf9Rf0R3LlOEw86aT8CdHRlm6\n"
+ + "wlb22xb8qoX4RB+LYfz7MhK5F+yLOPXZdJnAVbuyoMGRnDpwdzjL5Hj671+XJxN5\n"
+ + "SasRdhxkkfw/ZnHxaKEc4juMz8Nziz27elRwhOQqlTYoXNJnsV//wy5Losd7aKi1\n"
+ + "xXXyUpndEOmT0CIcKHrN/kbYoVL28OJaxoBuva3WYQaRrzEe3X02NMxZe9gkSqA=\n"
+ + "=TClh\n" + "-----END PGP SIGNATURE-----";
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
+ .getBytes(UTF_8));
+ b.write("type tree\n".getBytes(UTF_8));
+ b.write("tag v1.0\n".getBytes(UTF_8));
+ b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+ b.write('\n');
+ String message = "message\n\n-----BEGIN PGP SIGNATURE-----\n";
+ b.write(message.getBytes(UTF_8));
+ b.write(signature.getBytes(US_ASCII));
+ b.write('\n');
+
+ RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+ try (RevWalk rw = new RevWalk(db)) {
+ t.parseCanonical(rw, b.toByteArray());
+ }
+
+ assertEquals("t", t.getTaggerIdent().getName());
+ assertEquals("message", t.getShortMessage());
+ assertEquals(message + signature + '\n', t.getFullMessage());
+ String gpgSig = new String(t.getRawGpgSignature(), UTF_8);
+ assertEquals(signature + '\n', gpgSig);
+ }
+
@Test
public void testParse_NoMessage() throws Exception {
final String msg = "";
}
@Test
- public void testParse_PublicParseMethod() throws CorruptObjectException {
+ public void testParse_PublicParseMethod()
+ throws CorruptObjectException, UnsupportedEncodingException {
TagBuilder src = new TagBuilder();
try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
src.setObjectId(fmt.idFor(Constants.OBJ_TREE, new byte[] {}),
shortReadOfBlock=Short read of block.
shortReadOfOptionalDIRCExtensionExpectedAnotherBytes=Short read of optional DIRC extension {0}; expected another {1} bytes within the section.
shortSkipOfBlock=Short skip of block.
+signedTagMessageNoLf=A non-empty message of a signed tag must end in LF.
signingNotSupportedOnTag=Signing isn't supported on tag operations yet.
signingServiceUnavailable=Signing service is not available
similarityScoreMustBeWithinBounds=Similarity score must be between 0 and 100.
/***/ public String shortReadOfBlock;
/***/ public String shortReadOfOptionalDIRCExtensionExpectedAnotherBytes;
/***/ public String shortSkipOfBlock;
+ /***/ public String signedTagMessageNoLf;
/***/ public String signingNotSupportedOnTag;
/***/ public String signingServiceUnavailable;
/***/ public String similarityScoreMustBeWithinBounds;
/*
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
- * Copyright (C) 2006-2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2006, 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, 2020, Shawn O. Pearce <spearce@spearce.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
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
-import java.text.MessageFormat;
import java.util.List;
-import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.References;
/**
* and obtain a {@link org.eclipse.jgit.revwalk.RevCommit} instance by calling
* {@link org.eclipse.jgit.revwalk.RevWalk#parseCommit(AnyObjectId)}.
*/
-public class CommitBuilder {
+public class CommitBuilder extends ObjectBuilder {
private static final ObjectId[] EMPTY_OBJECTID_LIST = new ObjectId[0];
private static final byte[] htree = Constants.encodeASCII("tree"); //$NON-NLS-1$
private static final byte[] hgpgsig = Constants.encodeASCII("gpgsig"); //$NON-NLS-1$
- private static final byte[] hencoding = Constants.encodeASCII("encoding"); //$NON-NLS-1$
-
private ObjectId treeId;
private ObjectId[] parentIds;
- private PersonIdent author;
-
private PersonIdent committer;
- private GpgSignature gpgSignature;
-
- private String message;
-
- private Charset encoding;
-
/**
* Initialize an empty commit.
*/
public CommitBuilder() {
parentIds = EMPTY_OBJECTID_LIST;
- encoding = UTF_8;
}
/**
*
* @return the author of this commit (who wrote it).
*/
+ @Override
public PersonIdent getAuthor() {
- return author;
+ return super.getAuthor();
}
/**
* @param newAuthor
* the new author. Should not be null.
*/
+ @Override
public void setAuthor(PersonIdent newAuthor) {
- author = newAuthor;
+ super.setAuthor(newAuthor);
}
/**
committer = newCommitter;
}
- /**
- * Set the GPG signature of this commit.
- * <p>
- * Note, the signature set here will change the payload of the commit, i.e.
- * the output of {@link #build()} will include the signature. Thus, the
- * typical flow will be:
- * <ol>
- * <li>call {@link #build()} without a signature set to obtain payload</li>
- * <li>create {@link GpgSignature} from payload</li>
- * <li>set {@link GpgSignature}</li>
- * </ol>
- * </p>
- *
- * @param newSignature
- * the signature to set or <code>null</code> to unset
- * @since 5.3
- */
- public void setGpgSignature(GpgSignature newSignature) {
- gpgSignature = newSignature;
- }
-
- /**
- * Get the GPG signature of this commit.
- *
- * @return the GPG signature of this commit, maybe <code>null</code> if the
- * commit is not to be signed
- * @since 5.3
- */
- public GpgSignature getGpgSignature() {
- return gpgSignature;
- }
-
/**
* Get the ancestors of this commit.
*
}
}
- /**
- * Get the complete commit message.
- *
- * @return the complete commit message.
- */
- public String getMessage() {
- return message;
- }
-
- /**
- * Set the commit message.
- *
- * @param newMessage
- * the commit message. Should not be null.
- */
- public void setMessage(String newMessage) {
- message = newMessage;
- }
-
/**
* Set the encoding for the commit information.
*
*/
@Deprecated
public void setEncoding(String encodingName) {
- encoding = Charset.forName(encodingName);
- }
-
- /**
- * Set the encoding for the commit information.
- *
- * @param enc
- * the encoding to use.
- */
- public void setEncoding(Charset enc) {
- encoding = enc;
+ setEncoding(Charset.forName(encodingName));
}
- /**
- * Get the encoding that should be used for the commit message text.
- *
- * @return the encoding that should be used for the commit message text.
- */
- public Charset getEncoding() {
- return encoding;
- }
-
- /**
- * Format this builder's state as a commit object.
- *
- * @return this object in the canonical commit format, suitable for storage
- * in a repository.
- * @throws java.io.UnsupportedEncodingException
- * the encoding specified by {@link #getEncoding()} is not
- * supported by this Java runtime.
- */
+ @Override
public byte[] build() throws UnsupportedEncodingException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(os, getEncoding());
w.flush();
os.write('\n');
- if (getGpgSignature() != null) {
+ GpgSignature signature = getGpgSignature();
+ if (signature != null) {
os.write(hgpgsig);
os.write(' ');
- writeGpgSignatureString(getGpgSignature().toExternalString(), os);
+ writeMultiLineHeader(signature.toExternalString(), os,
+ true);
os.write('\n');
}
- if (!References.isSameObject(getEncoding(), UTF_8)) {
- os.write(hencoding);
- os.write(' ');
- os.write(Constants.encodeASCII(getEncoding().name()));
- os.write('\n');
- }
+ writeEncoding(getEncoding(), os);
os.write('\n');
return os.toByteArray();
}
- /**
- * Writes signature to output as per <a href=
- * "https://github.com/git/git/blob/master/Documentation/technical/signature-format.txt#L66,L89">gpgsig
- * header</a>.
- * <p>
- * CRLF and CR will be sanitized to LF and signature will have a hanging
- * indent of one space starting with line two. A trailing line break is
- * <em>not</em> written; the caller is supposed to terminate the GPG
- * signature header by writing a single newline.
- * </p>
- *
- * @param in
- * signature string with line breaks
- * @param out
- * output stream
- * @throws IOException
- * thrown by the output stream
- * @throws IllegalArgumentException
- * if the signature string contains non 7-bit ASCII chars
- */
- static void writeGpgSignatureString(String in, OutputStream out)
- throws IOException, IllegalArgumentException {
- int length = in.length();
- for (int i = 0; i < length; ++i) {
- char ch = in.charAt(i);
- switch (ch) {
- case '\r':
- if (i + 1 < length && in.charAt(i + 1) == '\n') {
- ++i;
- }
- if (i + 1 < length) {
- out.write('\n');
- out.write(' ');
- }
- break;
- case '\n':
- if (i + 1 < length) {
- out.write('\n');
- out.write(' ');
- }
- break;
- default:
- // sanity check
- if (ch > 127)
- throw new IllegalArgumentException(MessageFormat
- .format(JGitText.get().notASCIIString, in));
- out.write(ch);
- break;
- }
- }
- }
-
/**
* Format this builder's state as a commit object.
*
}
r.append("author ");
- r.append(author != null ? author.toString() : "NOT_SET");
+ r.append(getAuthor() != null ? getAuthor().toString() : "NOT_SET");
r.append("\n");
r.append("committer ");
r.append("\n");
r.append("gpgSignature ");
- r.append(gpgSignature != null ? gpgSignature.toString() : "NOT_SET");
+ GpgSignature signature = getGpgSignature();
+ r.append(signature != null ? signature.toString()
+ : "NOT_SET");
r.append("\n");
- if (encoding != null && !References.isSameObject(encoding, UTF_8)) {
+ Charset encoding = getEncoding();
+ if (!References.isSameObject(encoding, UTF_8)) {
r.append("encoding ");
r.append(encoding.name());
r.append("\n");
}
r.append("\n");
- r.append(message != null ? message : "");
+ r.append(getMessage() != null ? getMessage() : "");
r.append("}");
return r.toString();
}
--- /dev/null
+/*
+ * 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.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)
+ * @throws CanceledException
+ * when signing was canceled (eg., user aborted when entering
+ * passphrase)
+ */
+ void signObject(@NonNull ObjectBuilder object,
+ @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
+ CredentialsProvider credentialsProvider) throws CanceledException;
+
+}
--- /dev/null
+/*
+ * 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 static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.Objects;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.util.References;
+
+/**
+ * Common base class for {@link CommitBuilder} and {@link TagBuilder}.
+ *
+ * @since 5.11
+ */
+public abstract class ObjectBuilder {
+
+ /** Byte representation of "encoding". */
+ private static final byte[] hencoding = Constants.encodeASCII("encoding"); //$NON-NLS-1$
+
+ private PersonIdent author;
+
+ private GpgSignature gpgSignature;
+
+ private String message;
+
+ private Charset encoding = StandardCharsets.UTF_8;
+
+ /**
+ * Retrieves the author of this object.
+ *
+ * @return the author of this object, or {@code null} if not set yet
+ */
+ protected PersonIdent getAuthor() {
+ return author;
+ }
+
+ /**
+ * Sets the author (name, email address, and date) of this object.
+ *
+ * @param newAuthor
+ * the new author, must be non-{@code null}
+ */
+ protected void setAuthor(PersonIdent newAuthor) {
+ author = Objects.requireNonNull(newAuthor);
+ }
+
+ /**
+ * Sets the GPG signature of this object.
+ * <p>
+ * Note, the signature set here will change the payload of the object, i.e.
+ * the output of {@link #build()} will include the signature. Thus, the
+ * typical flow will be:
+ * <ol>
+ * <li>call {@link #build()} without a signature set to obtain payload</li>
+ * <li>create {@link GpgSignature} from payload</li>
+ * <li>set {@link GpgSignature}</li>
+ * </ol>
+ * </p>
+ *
+ * @param gpgSignature
+ * the signature to set or {@code null} to unset
+ * @since 5.3
+ */
+ public void setGpgSignature(@Nullable GpgSignature gpgSignature) {
+ this.gpgSignature = gpgSignature;
+ }
+
+ /**
+ * Retrieves the GPG signature of this object.
+ *
+ * @return the GPG signature of this object, or {@code null} if the object
+ * is not signed
+ * @since 5.3
+ */
+ @Nullable
+ public GpgSignature getGpgSignature() {
+ return gpgSignature;
+ }
+
+ /**
+ * Retrieves the complete message of the object.
+ *
+ * @return the complete message; can be {@code null}.
+ */
+ @Nullable
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message (commit message, or message of an annotated tag).
+ *
+ * @param message
+ * the message.
+ */
+ public void setMessage(@Nullable String message) {
+ this.message = message;
+ }
+
+ /**
+ * Retrieves the encoding that should be used for the message text.
+ *
+ * @return the encoding that should be used for the message text.
+ */
+ @NonNull
+ public Charset getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Sets the encoding for the object message.
+ *
+ * @param encoding
+ * the encoding to use.
+ */
+ public void setEncoding(@NonNull Charset encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * Format this builder's state as a git object.
+ *
+ * @return this object in the canonical git format, suitable for storage in
+ * a repository.
+ * @throws java.io.UnsupportedEncodingException
+ * the encoding specified by {@link #getEncoding()} is not
+ * supported by this Java runtime.
+ */
+ @NonNull
+ public abstract byte[] build() throws UnsupportedEncodingException;
+
+ /**
+ * Writes signature to output as per <a href=
+ * "https://github.com/git/git/blob/master/Documentation/technical/signature-format.txt#L66,L89">gpgsig
+ * header</a>.
+ * <p>
+ * CRLF and CR will be sanitized to LF and signature will have a hanging
+ * indent of one space starting with line two. A trailing line break is
+ * <em>not</em> written; the caller is supposed to terminate the GPG
+ * signature header by writing a single newline.
+ * </p>
+ *
+ * @param in
+ * signature string with line breaks
+ * @param out
+ * output stream
+ * @param enforceAscii
+ * whether to throw {@link IllegalArgumentException} if non-ASCII
+ * characters are encountered
+ * @throws IOException
+ * thrown by the output stream
+ * @throws IllegalArgumentException
+ * if the signature string contains non 7-bit ASCII chars and
+ * {@code enforceAscii == true}
+ */
+ static void writeMultiLineHeader(@NonNull String in,
+ @NonNull OutputStream out, boolean enforceAscii)
+ throws IOException, IllegalArgumentException {
+ int length = in.length();
+ for (int i = 0; i < length; ++i) {
+ char ch = in.charAt(i);
+ switch (ch) {
+ case '\r':
+ if (i + 1 < length && in.charAt(i + 1) == '\n') {
+ ++i;
+ }
+ if (i + 1 < length) {
+ out.write('\n');
+ out.write(' ');
+ }
+ break;
+ case '\n':
+ if (i + 1 < length) {
+ out.write('\n');
+ out.write(' ');
+ }
+ break;
+ default:
+ // sanity check
+ if (ch > 127 && enforceAscii)
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().notASCIIString, in));
+ out.write(ch);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Writes an "encoding" header.
+ *
+ * @param encoding
+ * to write
+ * @param out
+ * to write to
+ * @throws IOException
+ * if writing fails
+ */
+ static void writeEncoding(@NonNull Charset encoding,
+ @NonNull OutputStream out) throws IOException {
+ if (!References.isSameObject(encoding, UTF_8)) {
+ out.write(hencoding);
+ out.write(' ');
+ out.write(Constants.encodeASCII(encoding.name()));
+ out.write('\n');
+ }
+ }
+}
/*
- * Copyright (C) 2006-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * Copyright (C) 2010, 2020, Chris Aniszczyk <caniszczyk@gmail.com> 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
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.util.References;
/**
* Mutable builder to construct an annotated tag recording a project state.
* and obtain a {@link org.eclipse.jgit.revwalk.RevTag} instance by calling
* {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)}.
*/
-public class TagBuilder {
+public class TagBuilder extends ObjectBuilder {
+
+ private static final byte[] hobject = Constants.encodeASCII("object"); //$NON-NLS-1$
+
+ private static final byte[] htype = Constants.encodeASCII("type"); //$NON-NLS-1$
+
+ private static final byte[] htag = Constants.encodeASCII("tag"); //$NON-NLS-1$
+
+ private static final byte[] htagger = Constants.encodeASCII("tagger"); //$NON-NLS-1$
+
private ObjectId object;
private int type = Constants.OBJ_BAD;
private String tag;
- private PersonIdent tagger;
-
- private String message;
-
/**
* Get the type of object this tag refers to.
*
* @return creator of this tag. May be null.
*/
public PersonIdent getTagger() {
- return tagger;
+ return getAuthor();
}
/**
* the creator. May be null.
*/
public void setTagger(PersonIdent taggerIdent) {
- tagger = taggerIdent;
- }
-
- /**
- * Get the complete commit message.
- *
- * @return the complete commit message.
- */
- public String getMessage() {
- return message;
- }
-
- /**
- * Set the tag's message.
- *
- * @param newMessage
- * the tag's message.
- */
- public void setMessage(String newMessage) {
- message = newMessage;
+ setAuthor(taggerIdent);
}
/**
* @return this object in the canonical annotated tag format, suitable for
* storage in a repository.
*/
- public byte[] build() {
+ @Override
+ public byte[] build() throws UnsupportedEncodingException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (OutputStreamWriter w = new OutputStreamWriter(os,
- UTF_8)) {
- w.write("object "); //$NON-NLS-1$
- getObjectId().copyTo(w);
- w.write('\n');
+ getEncoding())) {
- w.write("type "); //$NON-NLS-1$
- w.write(Constants.typeString(getObjectType()));
- w.write("\n"); //$NON-NLS-1$
+ os.write(hobject);
+ os.write(' ');
+ getObjectId().copyTo(os);
+ os.write('\n');
- w.write("tag "); //$NON-NLS-1$
+ os.write(htype);
+ os.write(' ');
+ os.write(Constants
+ .encodeASCII(Constants.typeString(getObjectType())));
+ os.write('\n');
+
+ os.write(htag);
+ os.write(' ');
w.write(getTag());
- w.write("\n"); //$NON-NLS-1$
+ w.flush();
+ os.write('\n');
if (getTagger() != null) {
- w.write("tagger "); //$NON-NLS-1$
+ os.write(htagger);
+ os.write(' ');
w.write(getTagger().toExternalString());
- w.write('\n');
+ w.flush();
+ os.write('\n');
+ }
+
+ writeEncoding(getEncoding(), os);
+
+ os.write('\n');
+ String msg = getMessage();
+ if (msg != null) {
+ w.write(msg);
+ w.flush();
}
- w.write('\n');
- if (getMessage() != null)
- w.write(getMessage());
+ GpgSignature signature = getGpgSignature();
+ if (signature != null) {
+ if (msg != null && !msg.isEmpty() && !msg.endsWith("\n")) { //$NON-NLS-1$
+ // If signed, the message *must* end with a linefeed
+ // character, otherwise signature verification will fail.
+ // (The signature will have been computed over the payload
+ // containing the message without LF, but will be verified
+ // against a payload with the LF.) The signature must start
+ // on a new line.
+ throw new JGitInternalException(
+ JGitText.get().signedTagMessageNoLf);
+ }
+ String externalForm = signature.toExternalString();
+ w.write(externalForm);
+ w.flush();
+ if (!externalForm.endsWith("\n")) { //$NON-NLS-1$
+ os.write('\n');
+ }
+ }
} catch (IOException err) {
// This should never occur, the only way to get it above is
// for the ByteArrayOutputStream to throw, but it doesn't.
* Format this builder's state as an annotated tag object.
*
* @return this object in the canonical annotated tag format, suitable for
- * storage in a repository.
+ * storage in a repository, or {@code null} if the tag cannot be
+ * encoded
+ * @deprecated since 5.11; use {@link #build()} instead
*/
+ @Deprecated
public byte[] toByteArray() {
- return build();
+ try {
+ return build();
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
}
/** {@inheritDoc} */
r.append(tag != null ? tag : "NOT_SET");
r.append("\n");
- if (tagger != null) {
+ if (getTagger() != null) {
r.append("tagger ");
- r.append(tagger);
+ r.append(getTagger());
+ r.append("\n");
+ }
+
+ Charset encoding = getEncoding();
+ if (!References.isSameObject(encoding, UTF_8)) {
+ r.append("encoding ");
+ r.append(encoding.name());
r.append("\n");
}
r.append("\n");
- r.append(message != null ? message : "");
+ r.append(getMessage() != null ? getMessage() : "");
+ GpgSignature signature = getGpgSignature();
+ r.append(signature != null ? signature.toExternalString() : "");
r.append("}");
return r.toString();
}
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
+import java.util.Arrays;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
* An annotated tag.
*/
public class RevTag extends RevObject {
+
+ private static final byte[] hSignature = Constants
+ .encodeASCII("-----BEGIN PGP SIGNATURE-----"); //$NON-NLS-1$
+
/**
* Parse an annotated tag from its canonical format.
*
return RawParseUtils.parsePersonIdent(raw, nameB);
}
+ private static int nextStart(byte[] prefix, byte[] buffer, int from) {
+ int stop = buffer.length - prefix.length + 1;
+ int ptr = from;
+ if (ptr > 0) {
+ ptr = RawParseUtils.nextLF(buffer, ptr - 1);
+ }
+ while (ptr < stop) {
+ int lineStart = ptr;
+ boolean found = true;
+ for (byte element : prefix) {
+ if (element != buffer[ptr++]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return lineStart;
+ }
+ do {
+ ptr = RawParseUtils.nextLF(buffer, ptr);
+ } while (ptr < stop && buffer[ptr] == '\n');
+ }
+ return -1;
+ }
+
+ /**
+ * Parse the GPG signature from the raw buffer.
+ *
+ * @return contents of the GPG signature; {@code null} if the tag was not
+ * signed.
+ * @since 5.11
+ */
+ @Nullable
+ public final byte[] getRawGpgSignature() {
+ byte[] raw = buffer;
+ int msgB = RawParseUtils.tagMessage(raw, 0);
+ if (msgB < 0) {
+ return null;
+ }
+ // Find the last signature start and return the rest
+ int start = nextStart(hSignature, raw, msgB);
+ if (start < 0) {
+ return null;
+ }
+ int next = RawParseUtils.nextLF(raw, start);
+ while (next < raw.length) {
+ int newStart = nextStart(hSignature, raw, next);
+ if (newStart < 0) {
+ break;
+ }
+ start = newStart;
+ next = RawParseUtils.nextLF(raw, start);
+ }
+ return Arrays.copyOfRange(raw, start, raw.length);
+ }
+
/**
* Parse the complete tag message and decode it to a string.
* <p>