VerifySignatureCommand.java

  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. }