View Javadoc
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  
11  package org.eclipse.jgit.transport;
12  
13  import static org.eclipse.jgit.transport.PushCertificateParser.NONCE;
14  import static org.eclipse.jgit.transport.PushCertificateParser.PUSHEE;
15  import static org.eclipse.jgit.transport.PushCertificateParser.PUSHER;
16  import static org.eclipse.jgit.transport.PushCertificateParser.VERSION;
17  
18  import java.text.MessageFormat;
19  import java.util.List;
20  import java.util.Objects;
21  
22  import org.eclipse.jgit.internal.JGitText;
23  
24  /**
25   * The required information to verify the push.
26   * <p>
27   * A valid certificate will not return null from any getter methods; callers may
28   * assume that any null value indicates a missing or invalid certificate.
29   *
30   * @since 4.0
31   */
32  public class PushCertificate {
33  	/** Verification result of the nonce returned during push. */
34  	public enum NonceStatus {
35  		/** Nonce was not expected, yet client sent one anyway. */
36  		UNSOLICITED,
37  		/** Nonce is invalid and did not match server's expectations. */
38  		BAD,
39  		/** Nonce is required, but was not sent by client. */
40  		MISSING,
41  		/**
42  		 * Received nonce matches sent nonce, or is valid within the accepted slop
43  		 * window.
44  		 */
45  		OK,
46  		/** Received nonce is valid, but outside the accepted slop window. */
47  		SLOP
48  	}
49  
50  	private final String version;
51  	private final PushCertificateIdent pusher;
52  	private final String pushee;
53  	private final String nonce;
54  	private final NonceStatus nonceStatus;
55  	private final List<ReceiveCommand> commands;
56  	private final String signature;
57  
58  	PushCertificate(String version, PushCertificateIdent pusher, String pushee,
59  			String nonce, NonceStatus nonceStatus, List<ReceiveCommand> commands,
60  			String signature) {
61  		if (version == null || version.isEmpty()) {
62  			throw new IllegalArgumentException(MessageFormat.format(
63  					JGitText.get().pushCertificateInvalidField, VERSION));
64  		}
65  		if (pusher == null) {
66  			throw new IllegalArgumentException(MessageFormat.format(
67  					JGitText.get().pushCertificateInvalidField, PUSHER));
68  		}
69  		if (nonce == null || nonce.isEmpty()) {
70  			throw new IllegalArgumentException(MessageFormat.format(
71  					JGitText.get().pushCertificateInvalidField, NONCE));
72  		}
73  		if (nonceStatus == null) {
74  			throw new IllegalArgumentException(MessageFormat.format(
75  					JGitText.get().pushCertificateInvalidField,
76  					"nonce status")); //$NON-NLS-1$
77  		}
78  		if (commands == null || commands.isEmpty()) {
79  			throw new IllegalArgumentException(MessageFormat.format(
80  					JGitText.get().pushCertificateInvalidField,
81  					"command")); //$NON-NLS-1$
82  		}
83  		if (signature == null || signature.isEmpty()) {
84  			throw new IllegalArgumentException(
85  					JGitText.get().pushCertificateInvalidSignature);
86  		}
87  		if (!signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE)
88  				|| !signature.endsWith(PushCertificateParser.END_SIGNATURE + '\n')) {
89  			throw new IllegalArgumentException(
90  					JGitText.get().pushCertificateInvalidSignature);
91  		}
92  		this.version = version;
93  		this.pusher = pusher;
94  		this.pushee = pushee;
95  		this.nonce = nonce;
96  		this.nonceStatus = nonceStatus;
97  		this.commands = commands;
98  		this.signature = signature;
99  	}
100 
101 	/**
102 	 * Get the certificate version string.
103 	 *
104 	 * @return the certificate version string.
105 	 * @since 4.1
106 	 */
107 	public String getVersion() {
108 		return version;
109 	}
110 
111 	/**
112 	 * Get the raw line that signed the cert, as a string.
113 	 *
114 	 * @return the raw line that signed the cert, as a string.
115 	 * @since 4.0
116 	 */
117 	public String getPusher() {
118 		return pusher.getRaw();
119 	}
120 
121 	/**
122 	 * Get identity of the pusher who signed the cert.
123 	 *
124 	 * @return identity of the pusher who signed the cert.
125 	 * @since 4.1
126 	 */
127 	public PushCertificateIdent getPusherIdent() {
128 		return pusher;
129 	}
130 
131 	/**
132 	 * Get URL of the repository the push was originally sent to.
133 	 *
134 	 * @return URL of the repository the push was originally sent to.
135 	 * @since 4.0
136 	 */
137 	public String getPushee() {
138 		return pushee;
139 	}
140 
141 	/**
142 	 * Get the raw nonce value that was presented by the pusher.
143 	 *
144 	 * @return the raw nonce value that was presented by the pusher.
145 	 * @since 4.1
146 	 */
147 	public String getNonce() {
148 		return nonce;
149 	}
150 
151 	/**
152 	 * Get verification status of the nonce embedded in the certificate.
153 	 *
154 	 * @return verification status of the nonce embedded in the certificate.
155 	 * @since 4.0
156 	 */
157 	public NonceStatus getNonceStatus() {
158 		return nonceStatus;
159 	}
160 
161 	/**
162 	 * Get the list of commands as one string to be feed into the signature
163 	 * verifier.
164 	 *
165 	 * @return the list of commands as one string to be feed into the signature
166 	 *         verifier.
167 	 * @since 4.1
168 	 */
169 	public List<ReceiveCommand> getCommands() {
170 		return commands;
171 	}
172 
173 	/**
174 	 * Get the raw signature
175 	 *
176 	 * @return the raw signature, consisting of the lines received between the
177 	 *         lines {@code "----BEGIN GPG SIGNATURE-----\n"} and
178 	 *         {@code "----END GPG SIGNATURE-----\n}", inclusive.
179 	 * @since 4.0
180 	 */
181 	public String getSignature() {
182 		return signature;
183 	}
184 
185 	/**
186 	 * Get text payload of the certificate for the signature verifier.
187 	 *
188 	 * @return text payload of the certificate for the signature verifier.
189 	 * @since 4.1
190 	 */
191 	public String toText() {
192 		return toStringBuilder().toString();
193 	}
194 
195 	/**
196 	 * Get original text payload plus signature
197 	 *
198 	 * @return original text payload plus signature; the final output will be
199 	 *         valid as input to
200 	 *         {@link org.eclipse.jgit.transport.PushCertificateParser#fromString(String)}.
201 	 * @since 4.1
202 	 */
203 	public String toTextWithSignature() {
204 		return toStringBuilder().append(signature).toString();
205 	}
206 
207 	private StringBuilder toStringBuilder() {
208 		StringBuilder sb = new StringBuilder()
209 				.append(VERSION).append(' ').append(version).append('\n')
210 				.append(PUSHER).append(' ').append(getPusher())
211 				.append('\n');
212 		if (pushee != null) {
213 			sb.append(PUSHEE).append(' ').append(pushee).append('\n');
214 		}
215 		sb.append(NONCE).append(' ').append(nonce).append('\n')
216 				.append('\n');
217 		for (ReceiveCommand cmd : commands) {
218 			sb.append(cmd.getOldId().name())
219 				.append(' ').append(cmd.getNewId().name())
220 				.append(' ').append(cmd.getRefName()).append('\n');
221 		}
222 		return sb;
223 	}
224 
225 	/** {@inheritDoc} */
226 	@Override
227 	public int hashCode() {
228 		return signature.hashCode();
229 	}
230 
231 	/** {@inheritDoc} */
232 	@Override
233 	public boolean equals(Object o) {
234 		if (!(o instanceof PushCertificate)) {
235 			return false;
236 		}
237 		PushCertificate p = (PushCertificate) o;
238 		return version.equals(p.version)
239 				&& pusher.equals(p.pusher)
240 				&& Objects.equals(pushee, p.pushee)
241 				&& nonceStatus == p.nonceStatus
242 				&& signature.equals(p.signature)
243 				&& commandsEqual(this, p);
244 	}
245 
246 	private static boolean commandsEqual(PushCertificate../../../org/eclipse/jgit/transport/PushCertificate.html#PushCertificate">PushCertificate c1, PushCertificate c2) {
247 		if (c1.commands.size() != c2.commands.size()) {
248 			return false;
249 		}
250 		for (int i = 0; i < c1.commands.size(); i++) {
251 			ReceiveCommand cmd1 = c1.commands.get(i);
252 			ReceiveCommand cmd2 = c2.commands.get(i);
253 			if (!cmd1.getOldId().equals(cmd2.getOldId())
254 					|| !cmd1.getNewId().equals(cmd2.getNewId())
255 					|| !cmd1.getRefName().equals(cmd2.getRefName())) {
256 				return false;
257 			}
258 		}
259 		return true;
260 	}
261 
262 	/** {@inheritDoc} */
263 	@Override
264 	public String toString() {
265 		return getClass().getSimpleName() + '['
266 				 + toTextWithSignature() + ']';
267 	}
268 }