Browse Source

Parse signature of GPG-signed commits

In order to support GPG-signed commits, add some methods which will
allow GPG signatures to be parsed out of RevCommit objects.

Later, we can add code to verify the signatures.

Change-Id: Ifcf6b3ac79115c15d3ec4b4eaed07315534d09ac
Signed-off-by: David Turner <dturner@twosigma.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
stable-5.2
David Turner 7 years ago committed by Matthias Sohn
parent
commit
559c68cb01
  1. 32
      org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
  2. 43
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java
  3. 29
      org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
  4. 56
      org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java

32
org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java

@ -486,4 +486,36 @@ public class RevCommitParseTest extends RepositoryTestCase {
private static ObjectId id(String str) {
return ObjectId.fromString(str);
}
@Test
public void testParse_gpgSig() throws Exception {
String commit = "tree e3a1035abd2b319bb01e57d69b0ba6cab289297e\n" +
"parent 54e895b87c0768d2317a2b17062e3ad9f76a8105\n" +
"committer A U Thor <author@xample.com 1528968566 +0200\n" +
"gpgsig -----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-----\n" +
"some other header\n\n" +
"commit message";
final RevCommit c;
c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
c.parseCanonical(new RevWalk(db), commit.getBytes(UTF_8));
String gpgSig = new String(c.getRawGpgSignature(), UTF_8);
assertTrue(gpgSig.startsWith("-----BEGIN"));
assertTrue(gpgSig.endsWith("END PGP SIGNATURE-----"));
}
@Test
public void testParse_NoGpgSig() throws Exception {
final RevCommit c = create("a message");
assertNull(c.getRawGpgSignature());
}
}

43
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java

@ -52,7 +52,24 @@ import java.nio.charset.UnsupportedCharsetException;
import org.eclipse.jgit.lib.Constants;
import org.junit.Test;
import static java.nio.charset.StandardCharsets.UTF_8;
public class RawParseUtilsTest {
String commit = "tree e3a1035abd2b319bb01e57d69b0ba6cab289297e\n" +
"parent 54e895b87c0768d2317a2b17062e3ad9f76a8105\n" +
"committer A U Thor <author@xample.com 1528968566 +0200\n" +
"gpgsig -----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-----\n" +
"some other header\n\n" +
"commit message";
@Test
public void testParseEncoding_ISO8859_1_encoding() {
@ -79,4 +96,30 @@ public class RawParseUtilsTest {
}
}
@Test
public void testHeaderStart() {
byte[] headerName = "some".getBytes(UTF_8);
byte[] commitBytes = commit.getBytes(UTF_8);
assertEquals(625, RawParseUtils.headerStart(headerName, commitBytes, 0));
assertEquals(625, RawParseUtils.headerStart(headerName, commitBytes, 4));
byte[] missingHeaderName = "missing".getBytes(UTF_8);
assertEquals(-1, RawParseUtils.headerStart(missingHeaderName,
commitBytes, 0));
byte[] fauxHeaderName = "other".getBytes(UTF_8);
assertEquals(-1, RawParseUtils.headerStart(fauxHeaderName, commitBytes, 625 + 4));
}
@Test
public void testHeaderEnd() {
byte[] commitBytes = commit.getBytes(UTF_8);
int[] expected = new int[] {45, 93, 148, 619, 637};
int start = 0;
for (int i = 0; i < expected.length; i++) {
start = RawParseUtils.headerEnd(commitBytes, start);
assertEquals(expected[i], start);
start += 1;
}
}
}

29
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java

@ -51,6 +51,7 @@ import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -389,6 +390,34 @@ public class RevCommit extends RevObject {
return buffer;
}
/**
* Parse the gpg signature from the raw buffer.
* <p>
* This method parses and returns the raw content of the gpgsig lines. This
* method is fairly expensive and produces a new byte[] instance on each
* invocation. Callers should invoke this method only if they are certain
* they will need, and should cache the return value for as long as
* necessary to use all information from it.
* <p>
* RevFilter implementations should try to use
* {@link org.eclipse.jgit.util.RawParseUtils} to scan the
* {@link #getRawBuffer()} instead, as this will allow faster evaluation of
* commits.
*
* @return contents of the gpg signature; null if the commit was not signed.
* @since 5.1
*/
public final @Nullable byte[] getRawGpgSignature() {
final byte[] raw = buffer;
final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'};
final int start = RawParseUtils.headerStart(header, raw, 0);
if (start < 0) {
return null;
}
final int end = RawParseUtils.headerEnd(raw, start);
return Arrays.copyOfRange(raw, start, end);
}
/**
* Parse the author identity from the raw buffer.
* <p>

56
org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java

@ -548,6 +548,62 @@ public final class RawParseUtils {
return ptr;
}
/**
* Locate the end of the header. Note that headers may be
* more than one line long.
* @param b
* buffer to scan.
* @param ptr
* position within buffer to start looking for the end-of-header.
* @return new position just after the header. This is either
* b.length, or the index of the header's terminating newline.
* @since 5.1
*/
public static final int headerEnd(final byte[] b, int ptr) {
final int sz = b.length;
while (ptr < sz) {
final byte c = b[ptr++];
if (c == '\n' && (ptr == sz || b[ptr] != ' ')) {
return ptr - 1;
}
}
return ptr - 1;
}
/**
* Find the start of the contents of a given header.
*
* @param b
* buffer to scan.
* @param headerName
* header to search for
* @param ptr
* position within buffer to start looking for header at.
* @return new position at the start of the header's contents, -1 for
* not found
* @since 5.1
*/
public static final int headerStart(byte[] headerName, byte[] b, int ptr) {
// Start by advancing to just past a LF or buffer start
if (ptr != 0) {
ptr = nextLF(b, ptr - 1);
}
while (ptr < b.length - (headerName.length + 1)) {
boolean found = true;
for (int i = 0; i < headerName.length; i++) {
if (headerName[i] != b[ptr++]) {
found = false;
break;
}
}
if (found && b[ptr++] == ' ') {
return ptr;
}
ptr = nextLF(b, ptr);
}
return -1;
}
/**
* Locate the first position before a given character.
*

Loading…
Cancel
Save