You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VerifySignatureCommand.java 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /*
  2. * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.api;
  11. import java.io.IOException;
  12. import java.util.Arrays;
  13. import java.util.Collection;
  14. import java.util.HashMap;
  15. import java.util.HashSet;
  16. import java.util.Map;
  17. import java.util.Set;
  18. import org.eclipse.jgit.annotations.NonNull;
  19. import org.eclipse.jgit.api.errors.JGitInternalException;
  20. import org.eclipse.jgit.api.errors.ServiceUnavailableException;
  21. import org.eclipse.jgit.api.errors.WrongObjectTypeException;
  22. import org.eclipse.jgit.errors.MissingObjectException;
  23. import org.eclipse.jgit.internal.JGitText;
  24. import org.eclipse.jgit.lib.Constants;
  25. import org.eclipse.jgit.lib.GpgConfig;
  26. import org.eclipse.jgit.lib.GpgSignatureVerifier;
  27. import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification;
  28. import org.eclipse.jgit.lib.GpgSignatureVerifierFactory;
  29. import org.eclipse.jgit.lib.ObjectId;
  30. import org.eclipse.jgit.lib.Repository;
  31. import org.eclipse.jgit.revwalk.RevObject;
  32. import org.eclipse.jgit.revwalk.RevWalk;
  33. /**
  34. * A command to verify GPG signatures on tags or commits.
  35. *
  36. * @since 5.11
  37. */
  38. public class VerifySignatureCommand extends GitCommand<Map<String, VerificationResult>> {
  39. /**
  40. * Describes what kind of objects shall be handled by a
  41. * {@link VerifySignatureCommand}.
  42. */
  43. public enum VerifyMode {
  44. /**
  45. * Handle any object type, ignore anything that is not a commit or tag.
  46. */
  47. ANY,
  48. /**
  49. * Handle only commits; throw a {@link WrongObjectTypeException} for
  50. * anything else.
  51. */
  52. COMMITS,
  53. /**
  54. * Handle only tags; throw a {@link WrongObjectTypeException} for
  55. * anything else.
  56. */
  57. TAGS
  58. }
  59. private final Set<String> namesToCheck = new HashSet<>();
  60. private VerifyMode mode = VerifyMode.ANY;
  61. private GpgSignatureVerifier verifier;
  62. private GpgConfig config;
  63. private boolean ownVerifier;
  64. /**
  65. * Creates a new {@link VerifySignatureCommand} for the given {@link Repository}.
  66. *
  67. * @param repo
  68. * to operate on
  69. */
  70. public VerifySignatureCommand(Repository repo) {
  71. super(repo);
  72. }
  73. /**
  74. * Add a name of an object (SHA-1, ref name; anything that can be
  75. * {@link Repository#resolve(String) resolved}) to the command to have its
  76. * signature verified.
  77. *
  78. * @param name
  79. * to add
  80. * @return {@code this}
  81. */
  82. public VerifySignatureCommand addName(String name) {
  83. checkCallable();
  84. namesToCheck.add(name);
  85. return this;
  86. }
  87. /**
  88. * Add names of objects (SHA-1, ref name; anything that can be
  89. * {@link Repository#resolve(String) resolved}) to the command to have their
  90. * signatures verified.
  91. *
  92. * @param names
  93. * to add; duplicates will be ignored
  94. * @return {@code this}
  95. */
  96. public VerifySignatureCommand addNames(String... names) {
  97. checkCallable();
  98. namesToCheck.addAll(Arrays.asList(names));
  99. return this;
  100. }
  101. /**
  102. * Add names of objects (SHA-1, ref name; anything that can be
  103. * {@link Repository#resolve(String) resolved}) to the command to have their
  104. * signatures verified.
  105. *
  106. * @param names
  107. * to add; duplicates will be ignored
  108. * @return {@code this}
  109. */
  110. public VerifySignatureCommand addNames(Collection<String> names) {
  111. checkCallable();
  112. namesToCheck.addAll(names);
  113. return this;
  114. }
  115. /**
  116. * Sets the mode of operation for this command.
  117. *
  118. * @param mode
  119. * the {@link VerifyMode} to set
  120. * @return {@code this}
  121. */
  122. public VerifySignatureCommand setMode(@NonNull VerifyMode mode) {
  123. checkCallable();
  124. this.mode = mode;
  125. return this;
  126. }
  127. /**
  128. * Sets the {@link GpgSignatureVerifier} to use.
  129. *
  130. * @param verifier
  131. * the {@link GpgSignatureVerifier} to use, or {@code null} to
  132. * use the default verifier
  133. * @return {@code this}
  134. */
  135. public VerifySignatureCommand setVerifier(GpgSignatureVerifier verifier) {
  136. checkCallable();
  137. this.verifier = verifier;
  138. return this;
  139. }
  140. /**
  141. * Sets an external {@link GpgConfig} to use. Whether it will be used it at
  142. * the discretion of the {@link #setVerifier(GpgSignatureVerifier)}.
  143. *
  144. * @param config
  145. * to set; if {@code null}, the config will be loaded from the
  146. * git config of the repository
  147. * @return {@code this}
  148. * @since 5.11
  149. */
  150. public VerifySignatureCommand setGpgConfig(GpgConfig config) {
  151. checkCallable();
  152. this.config = config;
  153. return this;
  154. }
  155. /**
  156. * Retrieves the currently set {@link GpgSignatureVerifier}. Can be used
  157. * after a successful {@link #call()} to get the verifier that was used.
  158. *
  159. * @return the {@link GpgSignatureVerifier}
  160. */
  161. public GpgSignatureVerifier getVerifier() {
  162. return verifier;
  163. }
  164. /**
  165. * {@link Repository#resolve(String) Resolves} all names added to the
  166. * command to git objects and verifies their signature. Non-existing objects
  167. * are ignored.
  168. * <p>
  169. * Depending on the {@link #setMode(VerifyMode)}, only tags or commits or
  170. * any kind of objects are allowed.
  171. * </p>
  172. * <p>
  173. * Unsigned objects are silently skipped.
  174. * </p>
  175. *
  176. * @return a map of the given names to the corresponding
  177. * {@link VerificationResult}, excluding ignored or skipped objects.
  178. * @throws ServiceUnavailableException
  179. * if no {@link GpgSignatureVerifier} was set and no
  180. * {@link GpgSignatureVerifierFactory} is available
  181. * @throws WrongObjectTypeException
  182. * if a name resolves to an object of a type not allowed by the
  183. * {@link #setMode(VerifyMode)} mode
  184. */
  185. @Override
  186. @NonNull
  187. public Map<String, VerificationResult> call()
  188. throws ServiceUnavailableException, WrongObjectTypeException {
  189. checkCallable();
  190. setCallable(false);
  191. Map<String, VerificationResult> result = new HashMap<>();
  192. if (verifier == null) {
  193. GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory
  194. .getDefault();
  195. if (factory == null) {
  196. throw new ServiceUnavailableException(
  197. JGitText.get().signatureVerificationUnavailable);
  198. }
  199. verifier = factory.getVerifier();
  200. ownVerifier = true;
  201. }
  202. if (config == null) {
  203. config = new GpgConfig(repo.getConfig());
  204. }
  205. try (RevWalk walk = new RevWalk(repo)) {
  206. for (String toCheck : namesToCheck) {
  207. ObjectId id = repo.resolve(toCheck);
  208. if (id != null && !ObjectId.zeroId().equals(id)) {
  209. RevObject object;
  210. try {
  211. object = walk.parseAny(id);
  212. } catch (MissingObjectException e) {
  213. continue;
  214. }
  215. VerificationResult verification = verifyOne(object);
  216. if (verification != null) {
  217. result.put(toCheck, verification);
  218. }
  219. }
  220. }
  221. } catch (IOException e) {
  222. throw new JGitInternalException(
  223. JGitText.get().signatureVerificationError, e);
  224. } finally {
  225. if (ownVerifier) {
  226. verifier.clear();
  227. }
  228. }
  229. return result;
  230. }
  231. private VerificationResult verifyOne(RevObject object)
  232. throws WrongObjectTypeException, IOException {
  233. int type = object.getType();
  234. if (VerifyMode.TAGS.equals(mode) && type != Constants.OBJ_TAG) {
  235. throw new WrongObjectTypeException(object, Constants.OBJ_TAG);
  236. } else if (VerifyMode.COMMITS.equals(mode)
  237. && type != Constants.OBJ_COMMIT) {
  238. throw new WrongObjectTypeException(object, Constants.OBJ_COMMIT);
  239. }
  240. if (type == Constants.OBJ_COMMIT || type == Constants.OBJ_TAG) {
  241. try {
  242. GpgSignatureVerifier.SignatureVerification verification = verifier
  243. .verifySignature(object, config);
  244. if (verification == null) {
  245. // Not signed
  246. return null;
  247. }
  248. // Create new result
  249. return new Result(object, verification, null);
  250. } catch (JGitInternalException e) {
  251. return new Result(object, null, e);
  252. }
  253. }
  254. return null;
  255. }
  256. private static class Result implements VerificationResult {
  257. private final Throwable throwable;
  258. private final SignatureVerification verification;
  259. private final RevObject object;
  260. public Result(RevObject object, SignatureVerification verification,
  261. Throwable throwable) {
  262. this.object = object;
  263. this.verification = verification;
  264. this.throwable = throwable;
  265. }
  266. @Override
  267. public Throwable getException() {
  268. return throwable;
  269. }
  270. @Override
  271. public SignatureVerification getVerification() {
  272. return verification;
  273. }
  274. @Override
  275. public RevObject getObject() {
  276. return object;
  277. }
  278. }
  279. }