PushCertificate.java

  1. /*
  2.  * Copyright (C) 2015, Google Inc. 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.transport;

  11. import static org.eclipse.jgit.transport.PushCertificateParser.NONCE;
  12. import static org.eclipse.jgit.transport.PushCertificateParser.PUSHEE;
  13. import static org.eclipse.jgit.transport.PushCertificateParser.PUSHER;
  14. import static org.eclipse.jgit.transport.PushCertificateParser.VERSION;

  15. import java.text.MessageFormat;
  16. import java.util.List;
  17. import java.util.Objects;

  18. import org.eclipse.jgit.internal.JGitText;

  19. /**
  20.  * The required information to verify the push.
  21.  * <p>
  22.  * A valid certificate will not return null from any getter methods; callers may
  23.  * assume that any null value indicates a missing or invalid certificate.
  24.  *
  25.  * @since 4.0
  26.  */
  27. public class PushCertificate {
  28.     /** Verification result of the nonce returned during push. */
  29.     public enum NonceStatus {
  30.         /** Nonce was not expected, yet client sent one anyway. */
  31.         UNSOLICITED,
  32.         /** Nonce is invalid and did not match server's expectations. */
  33.         BAD,
  34.         /** Nonce is required, but was not sent by client. */
  35.         MISSING,
  36.         /**
  37.          * Received nonce matches sent nonce, or is valid within the accepted slop
  38.          * window.
  39.          */
  40.         OK,
  41.         /** Received nonce is valid, but outside the accepted slop window. */
  42.         SLOP
  43.     }

  44.     private final String version;
  45.     private final PushCertificateIdent pusher;
  46.     private final String pushee;
  47.     private final String nonce;
  48.     private final NonceStatus nonceStatus;
  49.     private final List<ReceiveCommand> commands;
  50.     private final String signature;

  51.     PushCertificate(String version, PushCertificateIdent pusher, String pushee,
  52.             String nonce, NonceStatus nonceStatus, List<ReceiveCommand> commands,
  53.             String signature) {
  54.         if (version == null || version.isEmpty()) {
  55.             throw new IllegalArgumentException(MessageFormat.format(
  56.                     JGitText.get().pushCertificateInvalidField, VERSION));
  57.         }
  58.         if (pusher == null) {
  59.             throw new IllegalArgumentException(MessageFormat.format(
  60.                     JGitText.get().pushCertificateInvalidField, PUSHER));
  61.         }
  62.         if (nonce == null || nonce.isEmpty()) {
  63.             throw new IllegalArgumentException(MessageFormat.format(
  64.                     JGitText.get().pushCertificateInvalidField, NONCE));
  65.         }
  66.         if (nonceStatus == null) {
  67.             throw new IllegalArgumentException(MessageFormat.format(
  68.                     JGitText.get().pushCertificateInvalidField,
  69.                     "nonce status")); //$NON-NLS-1$
  70.         }
  71.         if (commands == null || commands.isEmpty()) {
  72.             throw new IllegalArgumentException(MessageFormat.format(
  73.                     JGitText.get().pushCertificateInvalidField,
  74.                     "command")); //$NON-NLS-1$
  75.         }
  76.         if (signature == null || signature.isEmpty()) {
  77.             throw new IllegalArgumentException(
  78.                     JGitText.get().pushCertificateInvalidSignature);
  79.         }
  80.         if (!signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE)
  81.                 || !signature.endsWith(PushCertificateParser.END_SIGNATURE + '\n')) {
  82.             throw new IllegalArgumentException(
  83.                     JGitText.get().pushCertificateInvalidSignature);
  84.         }
  85.         this.version = version;
  86.         this.pusher = pusher;
  87.         this.pushee = pushee;
  88.         this.nonce = nonce;
  89.         this.nonceStatus = nonceStatus;
  90.         this.commands = commands;
  91.         this.signature = signature;
  92.     }

  93.     /**
  94.      * Get the certificate version string.
  95.      *
  96.      * @return the certificate version string.
  97.      * @since 4.1
  98.      */
  99.     public String getVersion() {
  100.         return version;
  101.     }

  102.     /**
  103.      * Get the raw line that signed the cert, as a string.
  104.      *
  105.      * @return the raw line that signed the cert, as a string.
  106.      * @since 4.0
  107.      */
  108.     public String getPusher() {
  109.         return pusher.getRaw();
  110.     }

  111.     /**
  112.      * Get identity of the pusher who signed the cert.
  113.      *
  114.      * @return identity of the pusher who signed the cert.
  115.      * @since 4.1
  116.      */
  117.     public PushCertificateIdent getPusherIdent() {
  118.         return pusher;
  119.     }

  120.     /**
  121.      * Get URL of the repository the push was originally sent to.
  122.      *
  123.      * @return URL of the repository the push was originally sent to.
  124.      * @since 4.0
  125.      */
  126.     public String getPushee() {
  127.         return pushee;
  128.     }

  129.     /**
  130.      * Get the raw nonce value that was presented by the pusher.
  131.      *
  132.      * @return the raw nonce value that was presented by the pusher.
  133.      * @since 4.1
  134.      */
  135.     public String getNonce() {
  136.         return nonce;
  137.     }

  138.     /**
  139.      * Get verification status of the nonce embedded in the certificate.
  140.      *
  141.      * @return verification status of the nonce embedded in the certificate.
  142.      * @since 4.0
  143.      */
  144.     public NonceStatus getNonceStatus() {
  145.         return nonceStatus;
  146.     }

  147.     /**
  148.      * Get the list of commands as one string to be feed into the signature
  149.      * verifier.
  150.      *
  151.      * @return the list of commands as one string to be feed into the signature
  152.      *         verifier.
  153.      * @since 4.1
  154.      */
  155.     public List<ReceiveCommand> getCommands() {
  156.         return commands;
  157.     }

  158.     /**
  159.      * Get the raw signature
  160.      *
  161.      * @return the raw signature, consisting of the lines received between the
  162.      *         lines {@code "----BEGIN GPG SIGNATURE-----\n"} and
  163.      *         {@code "----END GPG SIGNATURE-----\n}", inclusive.
  164.      * @since 4.0
  165.      */
  166.     public String getSignature() {
  167.         return signature;
  168.     }

  169.     /**
  170.      * Get text payload of the certificate for the signature verifier.
  171.      *
  172.      * @return text payload of the certificate for the signature verifier.
  173.      * @since 4.1
  174.      */
  175.     public String toText() {
  176.         return toStringBuilder().toString();
  177.     }

  178.     /**
  179.      * Get original text payload plus signature
  180.      *
  181.      * @return original text payload plus signature; the final output will be
  182.      *         valid as input to
  183.      *         {@link org.eclipse.jgit.transport.PushCertificateParser#fromString(String)}.
  184.      * @since 4.1
  185.      */
  186.     public String toTextWithSignature() {
  187.         return toStringBuilder().append(signature).toString();
  188.     }

  189.     private StringBuilder toStringBuilder() {
  190.         StringBuilder sb = new StringBuilder()
  191.                 .append(VERSION).append(' ').append(version).append('\n')
  192.                 .append(PUSHER).append(' ').append(getPusher())
  193.                 .append('\n');
  194.         if (pushee != null) {
  195.             sb.append(PUSHEE).append(' ').append(pushee).append('\n');
  196.         }
  197.         sb.append(NONCE).append(' ').append(nonce).append('\n')
  198.                 .append('\n');
  199.         for (ReceiveCommand cmd : commands) {
  200.             sb.append(cmd.getOldId().name())
  201.                 .append(' ').append(cmd.getNewId().name())
  202.                 .append(' ').append(cmd.getRefName()).append('\n');
  203.         }
  204.         return sb;
  205.     }

  206.     /** {@inheritDoc} */
  207.     @Override
  208.     public int hashCode() {
  209.         return signature.hashCode();
  210.     }

  211.     /** {@inheritDoc} */
  212.     @Override
  213.     public boolean equals(Object o) {
  214.         if (!(o instanceof PushCertificate)) {
  215.             return false;
  216.         }
  217.         PushCertificate p = (PushCertificate) o;
  218.         return version.equals(p.version)
  219.                 && pusher.equals(p.pusher)
  220.                 && Objects.equals(pushee, p.pushee)
  221.                 && nonceStatus == p.nonceStatus
  222.                 && signature.equals(p.signature)
  223.                 && commandsEqual(this, p);
  224.     }

  225.     private static boolean commandsEqual(PushCertificate c1, PushCertificate c2) {
  226.         if (c1.commands.size() != c2.commands.size()) {
  227.             return false;
  228.         }
  229.         for (int i = 0; i < c1.commands.size(); i++) {
  230.             ReceiveCommand cmd1 = c1.commands.get(i);
  231.             ReceiveCommand cmd2 = c2.commands.get(i);
  232.             if (!cmd1.getOldId().equals(cmd2.getOldId())
  233.                     || !cmd1.getNewId().equals(cmd2.getNewId())
  234.                     || !cmd1.getRefName().equals(cmd2.getRefName())) {
  235.                 return false;
  236.             }
  237.         }
  238.         return true;
  239.     }

  240.     /** {@inheritDoc} */
  241.     @Override
  242.     public String toString() {
  243.         return getClass().getSimpleName() + '['
  244.                  + toTextWithSignature() + ']';
  245.     }
  246. }