diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2024-02-19 23:02:20 +0000 |
---|---|---|
committer | Gerrit Code Review <support@gerrithub.io> | 2024-02-19 23:02:20 +0000 |
commit | 188cdcf7426bc80babe61bf02dabca290d4ef9df (patch) | |
tree | b3bcc14f841df5b0d88b60a074db37f588af5d3d | |
parent | c35deb6d8e0dfee8138a2e9eec7d384fd6ed162e (diff) | |
parent | 5afb92aadd2599b8209739df6fc8a95b8d7adadc (diff) | |
download | jgit-188cdcf7426bc80babe61bf02dabca290d4ef9df.tar.gz jgit-188cdcf7426bc80babe61bf02dabca290d4ef9df.zip |
Merge changes I9225d6a3,I9cf093ca,Id5a80635
* changes:
RawParseUtils.nextLfSkippingSplitLines: fulfil contract as stated
[gpg] Refactor the GpgSignatureVerifier
RawParseUtils: utility method to get a header value
7 files changed, 204 insertions, 63 deletions
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 7161895a6b..f4fed40973 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 @@ -15,7 +15,6 @@ import java.io.InputStream; import java.security.Security; import java.text.MessageFormat; import java.time.Instant; -import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.Locale; @@ -33,21 +32,18 @@ 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.annotations.Nullable; 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.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.util.LRUMap; -import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.StringUtils; /** * A {@link GpgSignatureVerifier} to verify GPG signatures using BouncyCastle. */ -public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier { +public class BouncyCastleGpgSignatureVerifier + extends AbstractGpgSignatureVerifier { private static void registerBouncyCastleProviderIfNecessary() { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { @@ -77,50 +73,6 @@ public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier { return "bc"; //$NON-NLS-1$ } - @Override - @Nullable - public SignatureVerification verifySignature(@NonNull RevObject object, - @NonNull 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.headerEnd(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(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(data, signatureData); - } - return null; - } - static PGPSignature parseSignature(InputStream in) throws IOException, PGPException { try (InputStream sigIn = PGPUtil.getDecoderStream(in)) { @@ -138,7 +90,8 @@ public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier { } @Override - public SignatureVerification verify(byte[] data, byte[] signatureData) + public SignatureVerification verify(@NonNull GpgConfig config, byte[] data, + byte[] signatureData) throws IOException { PGPSignature signature = null; String fingerprint = null; @@ -280,6 +233,13 @@ public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier { 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$ + } + private TrustLevel parseGpgTrustPacket(byte[] packet) { if (packet == null || packet.length < 6) { // A GPG trust packet has at least 6 bytes. diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java index e80c07509a..e4ef302359 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, Leonard Broman <leonard.broman@gmail.com> and others + * Copyright (C) 2011, 2024 Leonard Broman <leonard.broman@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 @@ -15,6 +15,7 @@ import static org.junit.Assert.fail; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; +import java.util.List; import org.eclipse.jgit.lib.Constants; import org.junit.Test; @@ -24,7 +25,7 @@ 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" + + "committer A U Thor <author@example.com> 1528968566 +0200\n" + "gpgsig -----BEGIN PGP SIGNATURE-----\n" + " \n" + " wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n" + @@ -67,21 +68,24 @@ public class RawParseUtilsTest { 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)); + assertEquals(627, + RawParseUtils.headerStart(headerName, commitBytes, 0)); + assertEquals(627, + 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)); + assertEquals(-1, RawParseUtils.headerStart(fauxHeaderName, commitBytes, + 627 + 4)); } @Test public void testHeaderEnd() { byte[] commitBytes = commit.getBytes(UTF_8); - int[] expected = new int[] {45, 93, 148, 619, 637}; + int[] expected = new int[] { 45, 93, 150, 621, 639 }; int start = 0; for (int i = 0; i < expected.length; i++) { start = RawParseUtils.headerEnd(commitBytes, start); @@ -89,4 +93,44 @@ public class RawParseUtilsTest { start += 1; } } + + @Test + public void testHeaderValue() { + byte[] commitBytes = commit.getBytes(UTF_8); + List<String> headers = List.of( + "e3a1035abd2b319bb01e57d69b0ba6cab289297e", + "54e895b87c0768d2317a2b17062e3ad9f76a8105", + "A U Thor <author@example.com> 1528968566 +0200", + "-----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-----", + "other header"); + int start = 0; + for (String header : headers) { + int endOfTag = RawParseUtils.next(commitBytes, start, ' '); + int end = RawParseUtils.headerEnd(commitBytes, start); + + assertEquals(header, new String( + RawParseUtils.headerValue(commitBytes, endOfTag, end), + UTF_8)); + start = end + 1; + } + } + + @Test + public void testLastHeaderEnd() { + byte[] raw = "headerA A header\nheaderB Another header".getBytes(UTF_8); + int bStart = RawParseUtils.headerStart("headerB".getBytes(UTF_8), raw, + 0); + assertEquals(25, bStart); + int bEnd = RawParseUtils.nextLfSkippingSplitLines(raw, bStart); + assertEquals(raw.length, bEnd); + } } diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index 2057347229..e15ed48e28 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -30,6 +30,14 @@ </message_arguments> </filter> </resource> + <resource path="src/org/eclipse/jgit/lib/GpgSignatureVerifier.java" type="org.eclipse.jgit.lib.GpgSignatureVerifier"> + <filter id="404000815"> + <message_arguments> + <message_argument value="org.eclipse.jgit.lib.GpgSignatureVerifier"/> + <message_argument value="verify(GpgConfig, byte[], byte[])"/> + </message_arguments> + </filter> + </resource> <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig"> <filter id="336658481"> <message_arguments> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractGpgSignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractGpgSignatureVerifier.java new file mode 100644 index 0000000000..06a89dc535 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractGpgSignatureVerifier.java @@ -0,0 +1,71 @@ +/* + * 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/GpgSignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java index a7a39c998f..91c9bab5a4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.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 @@ -18,7 +18,8 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.revwalk.RevObject; /** - * A {@code GpgVerifier} can verify GPG signatures on git commits and tags. + * A {@code GpgSignatureVerifier} can verify GPG signatures on git commits and + * tags. * * @since 5.11 */ @@ -42,6 +43,28 @@ public interface GpgSignatureVerifier { 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. @@ -55,7 +78,10 @@ public interface GpgSignatureVerifier { * 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java index 1f0f13152e..743a8ccce0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; -import java.util.Arrays; import java.util.List; import org.eclipse.jgit.annotations.Nullable; @@ -409,7 +408,7 @@ public class RevCommit extends RevObject { return null; } final int end = RawParseUtils.headerEnd(raw, start); - return Arrays.copyOfRange(raw, start, end); + return RawParseUtils.headerValue(raw, start, end); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java index c87a5e60f2..46d0bc85ff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java @@ -550,7 +550,40 @@ public final class RawParseUtils { return ptr - 1; } } - return ptr - 1; + return ptr; + } + + /** + * Extract a part of a buffer as a header value, removing the single blanks + * at the front of continuation lines. + * + * @param b + * buffer to extract the header from + * @param start + * of the header value, see + * {@link #headerStart(byte[], byte[], int)} + * @param end + * of the header; see + * {@link #nextLfSkippingSplitLines(byte[], int)} + * @return the header value, with blanks indicating continuation lines + * stripped + * @since 6.9 + */ + public static final byte[] headerValue(final byte[] b, int start, int end) { + byte[] data = new byte[end - start]; + int out = 0; + byte last = '\0'; + for (int in = start; in < end; in++) { + byte ch = b[in]; + if (ch != ' ' || last != '\n') { + data[out++] = ch; + } + last = ch; + } + if (out == data.length) { + return data; + } + return Arrays.copyOf(data, out); } /** |