Browse Source

Allow CommitCommand to sign commits

This change introduces the concept of a GpgSigner which will sign
commits. The GpgSigner will be of a specific implementation (eg.,
Bouncycastle or OpenPgP executable). The actual implementation is not
part of this change.

Bug: 382212
Change-Id: Iea5da1e885c039e06bc8d679d46b124cbe504c8e
Also-by: Medha Bhargav Prabhala <mprabhala@salesforce.com>
Signed-off-by: Medha Bhargav Prabhala <mprabhala@salesforce.com>
Signed-off-by: Gunnar Wagenknecht <gunnar@wagenknecht.org>
stable-5.3
Gunnar Wagenknecht 6 years ago
parent
commit
2343c688b1
  1. 99
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
  2. 201
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitBuilderTest.java
  3. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  4. 72
      org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
  5. 69
      org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnsupportedSigningFormatException.java
  6. 1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  7. 95
      org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
  8. 98
      org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java
  9. 100
      org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java

99
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java

@ -53,6 +53,7 @@ import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.api.errors.EmptyCommitException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
@ -61,9 +62,11 @@ import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.CommitBuilder;
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.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
@ -628,4 +631,100 @@ public class CommitCommandTest extends RepositoryTestCase {
builder.add(stage2);
builder.add(stage3);
}
@Test
public void callSignerWithProperSigningKey() throws Exception {
try (Git git = new Git(db)) {
writeTrashFile("file1", "file1");
git.add().addFilepattern("file1").call();
String[] signingKey = new String[1];
AtomicInteger callCount = new AtomicInteger();
GpgSigner.setDefault(new GpgSigner() {
@Override
public void sign(CommitBuilder commit, String gpgSigningKey) {
signingKey[0] = gpgSigningKey;
callCount.incrementAndGet();
}
});
// first call should use config, which is expected to be null at
// this time
git.commit().setSign(Boolean.TRUE).setMessage("initial commit")
.call();
assertNull(signingKey[0]);
assertEquals(1, callCount.get());
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
// second commit applies config value
String expectedConfigSigningKey = "config-" + System.nanoTime();
StoredConfig config = git.getRepository().getConfig();
config.setString("user", null, "signingKey",
expectedConfigSigningKey);
config.save();
git.commit().setSign(Boolean.TRUE).setMessage("initial commit")
.call();
assertEquals(expectedConfigSigningKey, signingKey[0]);
assertEquals(2, callCount.get());
writeTrashFile("file3", "file3");
git.add().addFilepattern("file3").call();
// now use specific on api
String expectedSigningKey = "my-" + System.nanoTime();
git.commit().setSign(Boolean.TRUE).setSigningKey(expectedSigningKey)
.setMessage("initial commit").call();
assertEquals(expectedSigningKey, signingKey[0]);
assertEquals(3, callCount.get());
}
}
@Test
public void callSignerOnlyWhenSigning() throws Exception {
try (Git git = new Git(db)) {
writeTrashFile("file1", "file1");
git.add().addFilepattern("file1").call();
AtomicInteger callCount = new AtomicInteger();
GpgSigner.setDefault(new GpgSigner() {
@Override
public void sign(CommitBuilder commit, String gpgSigningKey) {
callCount.incrementAndGet();
}
});
// first call should use config, which is expected to be null at
// this time
git.commit().setMessage("initial commit").call();
assertEquals(0, callCount.get());
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
// now force signing
git.commit().setSign(Boolean.TRUE).setMessage("commit").call();
assertEquals(1, callCount.get());
writeTrashFile("file3", "file3");
git.add().addFilepattern("file3").call();
// now rely on config
StoredConfig config = git.getRepository().getConfig();
config.setBoolean("commit", null, "gpgSign", true);
config.save();
git.commit().setMessage("commit").call();
assertEquals(2, callCount.get());
writeTrashFile("file4", "file4");
git.add().addFilepattern("file4").call();
// now force "no-sign" (even though config is true)
git.commit().setSign(Boolean.FALSE).setMessage("commit").call();
assertEquals(2, callCount.get());
}
}
}

201
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitBuilderTest.java

@ -0,0 +1,201 @@
/*
* Copyright (C) 2018, Salesforce.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.lib;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import org.eclipse.jgit.internal.JGitText;
import org.junit.Test;
public class CommitBuilderTest {
private void assertGpgSignatureStringOutcome(String signature,
String expectedOutcome) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
CommitBuilder.writeGpgSignatureString(signature, out);
String formatted_signature = new String(out.toByteArray(), US_ASCII);
assertEquals(expectedOutcome, formatted_signature);
}
@Test
public void writeGpgSignatureString_1() throws Exception {
// @formatter:off
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-----";
String expectedOutcome = "-----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
assertGpgSignatureStringOutcome(signature, expectedOutcome);
}
@Test
public void writeGpgSignatureString_failsForNonAscii() throws Exception {
String signature = "Ü Ä";
try {
CommitBuilder.writeGpgSignatureString(signature,
new ByteArrayOutputStream());
fail("Exception expected");
} catch (IllegalArgumentException e) {
// good
String message = MessageFormat.format(JGitText.get().notASCIIString,
signature);
assertEquals(message, e.getMessage());
}
}
@Test
public void writeGpgSignatureString_oneLineNotModified() throws Exception {
String signature = " A string ";
String expectedOutcome = signature;
assertGpgSignatureStringOutcome(signature, expectedOutcome);
}
@Test
public void writeGpgSignatureString_preservesRandomWhitespace()
throws Exception {
// @formatter:off
String signature = " String with \n"
+ "Line 2\n"
+ " Line 3\n"
+ "Line 4 \n"
+ " Line 5 ";
String expectedOutcome = " String with \n"
+ " Line 2\n"
+ " Line 3\n"
+ " Line 4 \n"
+ " Line 5 ";
// @formatter:on
assertGpgSignatureStringOutcome(signature, expectedOutcome);
}
@Test
public void writeGpgSignatureString_replaceCR() throws Exception {
// @formatter:off
String signature = "String with \r"
+ "Line 2\r"
+ "Line 3\r"
+ "Line 4\r"
+ "Line 5";
String expectedOutcome = "String with \n"
+ " Line 2\n"
+ " Line 3\n"
+ " Line 4\n"
+ " Line 5";
// @formatter:on
assertGpgSignatureStringOutcome(signature, expectedOutcome);
}
@Test
public void writeGpgSignatureString_replaceCRLF() throws Exception {
// @formatter:off
String signature = "String with \r\n"
+ "Line 2\r\n"
+ "Line 3\r\n"
+ "Line 4\r\n"
+ "Line 5";
String expectedOutcome = "String with \n"
+ " Line 2\n"
+ " Line 3\n"
+ " Line 4\n"
+ " Line 5";
// @formatter:on
assertGpgSignatureStringOutcome(signature, expectedOutcome);
}
@Test
public void writeGpgSignatureString_replaceCRLFMixed() throws Exception {
// @formatter:off
String signature = "String with \r"
+ "Line 2\r\n"
+ "Line 3\r"
+ "Line 4\r\n"
+ "Line 5";
String expectedOutcome = "String with \n"
+ " Line 2\n"
+ " Line 3\n"
+ " Line 4\n"
+ " Line 5";
// @formatter:on
assertGpgSignatureStringOutcome(signature, expectedOutcome);
}
@Test
public void setGpgSignature() throws Exception {
GpgSignature dummy = new GpgSignature(new byte[0]);
CommitBuilder builder = new CommitBuilder();
assertNull(builder.getGpgSignature());
builder.setGpgSignature(dummy);
assertSame(dummy, builder.getGpgSignature());
builder.setGpgSignature(null);
assertNull(builder.getGpgSignature());
}
}

1
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

@ -505,6 +505,7 @@ oldIdMustNotBeNull=Expected old ID must not be null
onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available
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

72
org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java

@ -61,6 +61,7 @@ import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
@ -76,6 +77,9 @@ import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
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.GpgSigner;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
@ -139,6 +143,12 @@ public class CommitCommand extends GitCommand<RevCommit> {
private Boolean allowEmpty;
private Boolean signCommit;
private String signingKey;
private GpgSigner gpgSigner;
/**
* Constructor for CommitCommand
*
@ -251,6 +261,11 @@ public class CommitCommand extends GitCommand<RevCommit> {
commit.setParentIds(parents);
commit.setTreeId(indexTreeId);
if (signCommit.booleanValue()) {
gpgSigner.sign(commit, signingKey);
}
ObjectId commitId = odi.insert(commit);
odi.flush();
@ -517,9 +532,10 @@ public class CommitCommand extends GitCommand<RevCommit> {
*
* @throws NoMessageException
* if the commit message has not been specified
* @throws UnsupportedSigningFormatException if the configured gpg.format is not supported
*/
private void processOptions(RepositoryState state, RevWalk rw)
throws NoMessageException {
throws NoMessageException, UnsupportedSigningFormatException {
if (committer == null)
committer = new PersonIdent(repo);
if (author == null && !amend)
@ -572,6 +588,22 @@ public class CommitCommand extends GitCommand<RevCommit> {
// as long as we don't support -C option we have to have
// an explicit message
throw new NoMessageException(JGitText.get().commitMessageNotSpecified);
GpgConfig gpgConfig = new GpgConfig(repo.getConfig());
if (signCommit == null) {
signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE
: Boolean.FALSE;
}
if (signingKey == null) {
signingKey = gpgConfig.getSigningKey();
}
if (gpgSigner == null) {
if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
throw new UnsupportedSigningFormatException(
JGitText.get().onlyOpenPgpSupportedForSigning);
}
gpgSigner = GpgSigner.getDefault();
}
}
private boolean isMergeDuringRebase(RepositoryState state) {
@ -873,4 +905,42 @@ public class CommitCommand extends GitCommand<RevCommit> {
hookOutRedirect.put(hookName, hookStdOut);
return this;
}
/**
* Sets the signing key
* <p>
* Per spec of user.signingKey: this will be sent to the GPG program as is,
* i.e. can be anything supported by the GPG program.
* </p>
* <p>
* Note, if none was set or <code>null</code> is specified a default will be
* obtained from the configuration.
* </p>
*
* @param signingKey
* signing key (maybe <code>null</code>)
* @return {@code this}
* @since 5.3
*/
public CommitCommand setSigningKey(String signingKey) {
checkCallable();
this.signingKey = signingKey;
return this;
}
/**
* Sets whether the commit should be signed.
*
* @param sign
* <code>true</code> to sign, <code>false</code> to not sign and
* <code>null</code> for default behavior (read from
* configuration)
* @return {@code this}
* @since 5.3
*/
public CommitCommand setSign(Boolean sign) {
checkCallable();
this.signCommit = sign;
return this;
}
}

69
org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnsupportedSigningFormatException.java

@ -0,0 +1,69 @@
/*
* Copyright (C) 2018, Salesforce and
* other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v1.0 which accompanies this
* distribution, is reproduced below, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.api.errors;
/**
* Exception thrown when the configured gpg.format is not supported.
*
* @since 5.3
*/
public class UnsupportedSigningFormatException extends GitAPIException {
private static final long serialVersionUID = 1L;
/**
* Constructor for UnsupportedGpgFormatException
*
* @param message
* error message
* @param cause
* a {@link java.lang.Throwable}
*/
public UnsupportedSigningFormatException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructor for UnsupportedGpgFormatException
*
* @param message
* error message
*/
public UnsupportedSigningFormatException(String message) {
super(message);
}
}

1
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

@ -566,6 +566,7 @@ public class JGitText extends TranslationBundle {
/***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable;
/***/ public String onlyOneFetchSupported;
/***/ public String onlyOneOperationCallPerConnectionIsSupported;
/***/ public String onlyOpenPgpSupportedForSigning;
/***/ public String openFilesMustBeAtLeast1;
/***/ public String openingConnection;
/***/ public String operationCanceled;

95
org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java

@ -49,11 +49,15 @@ import static java.nio.charset.StandardCharsets.UTF_8;
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;
/**
* Mutable builder to construct a commit recording the state of a project.
*
@ -76,6 +80,8 @@ public class CommitBuilder {
private static final byte[] hcommitter = Constants.encodeASCII("committer"); //$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;
@ -86,6 +92,8 @@ public class CommitBuilder {
private PersonIdent committer;
private GpgSignature gpgSignature;
private String message;
private Charset encoding;
@ -155,6 +163,38 @@ public class CommitBuilder {
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.
*
@ -316,6 +356,13 @@ public class CommitBuilder {
w.flush();
os.write('\n');
if (getGpgSignature() != null) {
os.write(hgpgsig);
os.write(' ');
writeGpgSignatureString(getGpgSignature().toExternalString(), os);
os.write('\n');
}
if (getEncoding() != UTF_8) {
os.write(hencoding);
os.write(' ');
@ -338,6 +385,50 @@ public class CommitBuilder {
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.
* </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 {
for (int i = 0; i < in.length(); ++i) {
char ch = in.charAt(i);
if (ch == '\r') {
if (i + 1 < in.length() && in.charAt(i + 1) == '\n') {
out.write('\n');
out.write(' ');
++i;
} else {
out.write('\n');
out.write(' ');
}
} else if (ch == '\n') {
out.write('\n');
out.write(' ');
} else {
// sanity check
if (ch > 127)
throw new IllegalArgumentException(MessageFormat
.format(JGitText.get().notASCIIString, in));
out.write(ch);
}
}
}
/**
* Format this builder's state as a commit object.
*
@ -377,6 +468,10 @@ public class CommitBuilder {
r.append(committer != null ? committer.toString() : "NOT_SET");
r.append("\n");
r.append("gpgSignature ");
r.append(gpgSignature != null ? gpgSignature.toString() : "NOT_SET");
r.append("\n");
if (encoding != null && encoding != UTF_8) {
r.append("encoding ");
r.append(encoding.name());

98
org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java

@ -0,0 +1,98 @@
/*
* Copyright (C) 2018, Salesforce.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.lib;
import static java.nio.charset.StandardCharsets.US_ASCII;
import java.io.Serializable;
import org.eclipse.jgit.annotations.NonNull;
/**
* A structure for holding GPG signature together with additional related data.
*
* @since 5.3
*/
public class GpgSignature implements Serializable {
private static final long serialVersionUID = 1L;
private byte[] signature;
/**
* Creates a new instance with the specified signature
*
* @param signature
* the signature
*/
public GpgSignature(@NonNull byte[] signature) {
this.signature = signature;
}
/**
* Format for Git storage.
* <p>
* This returns the ASCII Armor as per
* https://tools.ietf.org/html/rfc4880#section-6.2.
* </p>
*
* @return a string of the signature ready to be embedded in a Git object
*/
public String toExternalString() {
return new String(signature, US_ASCII);
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("nls")
public String toString() {
final StringBuilder r = new StringBuilder();
r.append("GpgSignature[");
r.append(
this.signature != null ? "length " + signature.length : "null");
r.append("]");
return r.toString();
}
}

100
org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java

@ -0,0 +1,100 @@
/*
* Copyright (C) 2018, Salesforce.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.lib;
import org.eclipse.jgit.annotations.NonNull;
/**
* Creates GPG signatures for Git objects.
*
* @since 5.3
*/
public abstract class GpgSigner {
private static GpgSigner defaultSigner;
/**
* Get the default signer, or <code>null</code>.
*
* @return the default signer, or <code>null</code>.
*/
public static GpgSigner getDefault() {
return defaultSigner;
}
/**
* 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) {
GpgSigner.defaultSigner = 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 (passed as is to the GPG signing tool)
*/
public abstract void sign(@NonNull CommitBuilder commit,
String gpgSigningKey);
}
Loading…
Cancel
Save