aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java
blob: 487ff043237ebadbfb54bf8232534970a6f509d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/*
 * Copyright (C) 2021, 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.api;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.ServiceUnavailableException;
import org.eclipse.jgit.api.errors.WrongObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.GpgConfig;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification;
import org.eclipse.jgit.lib.SignatureVerifiers;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;

/**
 * A command to verify GPG signatures on tags or commits.
 *
 * @since 5.11
 */
public class VerifySignatureCommand extends GitCommand<Map<String, VerificationResult>> {

	/**
	 * Describes what kind of objects shall be handled by a
	 * {@link VerifySignatureCommand}.
	 */
	public enum VerifyMode {
		/**
		 * Handle any object type, ignore anything that is not a commit or tag.
		 */
		ANY,
		/**
		 * Handle only commits; throw a {@link WrongObjectTypeException} for
		 * anything else.
		 */
		COMMITS,
		/**
		 * Handle only tags; throw a {@link WrongObjectTypeException} for
		 * anything else.
		 */
		TAGS
	}

	private final Set<String> namesToCheck = new HashSet<>();

	private VerifyMode mode = VerifyMode.ANY;

	private GpgConfig config;

	/**
	 * Creates a new {@link VerifySignatureCommand} for the given {@link Repository}.
	 *
	 * @param repo
	 *            to operate on
	 */
	public VerifySignatureCommand(Repository repo) {
		super(repo);
	}

	/**
	 * Add a name of an object (SHA-1, ref name; anything that can be
	 * {@link Repository#resolve(String) resolved}) to the command to have its
	 * signature verified.
	 *
	 * @param name
	 *            to add
	 * @return {@code this}
	 */
	public VerifySignatureCommand addName(String name) {
		checkCallable();
		namesToCheck.add(name);
		return this;
	}

	/**
	 * Add names of objects (SHA-1, ref name; anything that can be
	 * {@link Repository#resolve(String) resolved}) to the command to have their
	 * signatures verified.
	 *
	 * @param names
	 *            to add; duplicates will be ignored
	 * @return {@code this}
	 */
	public VerifySignatureCommand addNames(String... names) {
		checkCallable();
		namesToCheck.addAll(Arrays.asList(names));
		return this;
	}

	/**
	 * Add names of objects (SHA-1, ref name; anything that can be
	 * {@link Repository#resolve(String) resolved}) to the command to have their
	 * signatures verified.
	 *
	 * @param names
	 *            to add; duplicates will be ignored
	 * @return {@code this}
	 */
	public VerifySignatureCommand addNames(Collection<String> names) {
		checkCallable();
		namesToCheck.addAll(names);
		return this;
	}

	/**
	 * Sets the mode of operation for this command.
	 *
	 * @param mode
	 *            the {@link VerifyMode} to set
	 * @return {@code this}
	 */
	public VerifySignatureCommand setMode(@NonNull VerifyMode mode) {
		checkCallable();
		this.mode = mode;
		return this;
	}

	/**
	 * Sets an external {@link GpgConfig} to use.
	 *
	 * @param config
	 *            to set; if {@code null}, the config will be loaded from the
	 *            git config of the repository
	 * @return {@code this}
	 * @since 5.11
	 */
	public VerifySignatureCommand setGpgConfig(GpgConfig config) {
		checkCallable();
		this.config = config;
		return this;
	}

	/**
	 * {@link Repository#resolve(String) Resolves} all names added to the
	 * command to git objects and verifies their signature. Non-existing objects
	 * are ignored.
	 * <p>
	 * Depending on the {@link #setMode(VerifyMode)}, only tags or commits or
	 * any kind of objects are allowed.
	 * </p>
	 * <p>
	 * Unsigned objects are silently skipped.
	 * </p>
	 *
	 * @return a map of the given names to the corresponding
	 *         {@link VerificationResult}, excluding ignored or skipped objects.
	 * @throws WrongObjectTypeException
	 *             if a name resolves to an object of a type not allowed by the
	 *             {@link #setMode(VerifyMode)} mode
	 */
	@Override
	@NonNull
	public Map<String, VerificationResult> call()
			throws ServiceUnavailableException, WrongObjectTypeException {
		checkCallable();
		setCallable(false);
		Map<String, VerificationResult> result = new HashMap<>();
		if (config == null) {
			config = new GpgConfig(repo.getConfig());
		}
		try (RevWalk walk = new RevWalk(repo)) {
			for (String toCheck : namesToCheck) {
				ObjectId id = repo.resolve(toCheck);
				if (id != null && !ObjectId.zeroId().equals(id)) {
					RevObject object;
					try {
						object = walk.parseAny(id);
					} catch (MissingObjectException e) {
						continue;
					}
					VerificationResult verification = verifyOne(object);
					if (verification != null) {
						result.put(toCheck, verification);
					}
				}
			}
		} catch (IOException e) {
			throw new JGitInternalException(
					JGitText.get().signatureVerificationError, e);
		}
		return result;
	}

	private VerificationResult verifyOne(RevObject object)
			throws WrongObjectTypeException, IOException {
		int type = object.getType();
		if (VerifyMode.TAGS.equals(mode) && type != Constants.OBJ_TAG) {
			throw new WrongObjectTypeException(object, Constants.OBJ_TAG);
		} else if (VerifyMode.COMMITS.equals(mode)
				&& type != Constants.OBJ_COMMIT) {
			throw new WrongObjectTypeException(object, Constants.OBJ_COMMIT);
		}
		if (type == Constants.OBJ_COMMIT || type == Constants.OBJ_TAG) {
			try {
				SignatureVerification verification = SignatureVerifiers
						.verify(repo, config, object);
				if (verification == null) {
					// Not signed
					return null;
				}
				// Create new result
				return new Result(object, verification, null);
			} catch (JGitInternalException e) {
				return new Result(object, null, e);
			}
		}
		return null;
	}

	private static class Result implements VerificationResult {

		private final Throwable throwable;

		private final SignatureVerification verification;

		private final RevObject object;

		public Result(RevObject object, SignatureVerification verification,
				Throwable throwable) {
			this.object = object;
			this.verification = verification;
			this.throwable = throwable;
		}

		@Override
		public Throwable getException() {
			return throwable;
		}

		@Override
		public SignatureVerification getVerification() {
			return verification;
		}

		@Override
		public RevObject getObject() {
			return object;
		}

	}
}