View Javadoc
1   /*
2    * Copyright (C) 2015, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import static org.eclipse.jgit.transport.PushCertificateParser.NONCE;
47  import static org.eclipse.jgit.transport.PushCertificateParser.PUSHEE;
48  import static org.eclipse.jgit.transport.PushCertificateParser.PUSHER;
49  import static org.eclipse.jgit.transport.PushCertificateParser.VERSION;
50  
51  import java.text.MessageFormat;
52  import java.util.List;
53  import java.util.Objects;
54  
55  import org.eclipse.jgit.internal.JGitText;
56  
57  /**
58   * The required information to verify the push.
59   * <p>
60   * A valid certificate will not return null from any getter methods; callers may
61   * assume that any null value indicates a missing or invalid certificate.
62   *
63   * @since 4.0
64   */
65  public class PushCertificate {
66  	/** Verification result of the nonce returned during push. */
67  	public enum NonceStatus {
68  		/** Nonce was not expected, yet client sent one anyway. */
69  		UNSOLICITED,
70  		/** Nonce is invalid and did not match server's expectations. */
71  		BAD,
72  		/** Nonce is required, but was not sent by client. */
73  		MISSING,
74  		/**
75  		 * Received nonce matches sent nonce, or is valid within the accepted slop
76  		 * window.
77  		 */
78  		OK,
79  		/** Received nonce is valid, but outside the accepted slop window. */
80  		SLOP
81  	}
82  
83  	private final String version;
84  	private final PushCertificateIdent pusher;
85  	private final String pushee;
86  	private final String nonce;
87  	private final NonceStatus nonceStatus;
88  	private final List<ReceiveCommand> commands;
89  	private final String signature;
90  
91  	PushCertificate(String version, PushCertificateIdent pusher, String pushee,
92  			String nonce, NonceStatus nonceStatus, List<ReceiveCommand> commands,
93  			String signature) {
94  		if (version == null || version.isEmpty()) {
95  			throw new IllegalArgumentException(MessageFormat.format(
96  					JGitText.get().pushCertificateInvalidField, VERSION));
97  		}
98  		if (pusher == null) {
99  			throw new IllegalArgumentException(MessageFormat.format(
100 					JGitText.get().pushCertificateInvalidField, PUSHER));
101 		}
102 		if (nonce == null || nonce.isEmpty()) {
103 			throw new IllegalArgumentException(MessageFormat.format(
104 					JGitText.get().pushCertificateInvalidField, NONCE));
105 		}
106 		if (nonceStatus == null) {
107 			throw new IllegalArgumentException(MessageFormat.format(
108 					JGitText.get().pushCertificateInvalidField,
109 					"nonce status")); //$NON-NLS-1$
110 		}
111 		if (commands == null || commands.isEmpty()) {
112 			throw new IllegalArgumentException(MessageFormat.format(
113 					JGitText.get().pushCertificateInvalidField,
114 					"command")); //$NON-NLS-1$
115 		}
116 		if (signature == null || signature.isEmpty()) {
117 			throw new IllegalArgumentException(
118 					JGitText.get().pushCertificateInvalidSignature);
119 		}
120 		if (!signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE)
121 				|| !signature.endsWith(PushCertificateParser.END_SIGNATURE + '\n')) {
122 			throw new IllegalArgumentException(
123 					JGitText.get().pushCertificateInvalidSignature);
124 		}
125 		this.version = version;
126 		this.pusher = pusher;
127 		this.pushee = pushee;
128 		this.nonce = nonce;
129 		this.nonceStatus = nonceStatus;
130 		this.commands = commands;
131 		this.signature = signature;
132 	}
133 
134 	/**
135 	 * Get the certificate version string.
136 	 *
137 	 * @return the certificate version string.
138 	 * @since 4.1
139 	 */
140 	public String getVersion() {
141 		return version;
142 	}
143 
144 	/**
145 	 * Get the raw line that signed the cert, as a string.
146 	 *
147 	 * @return the raw line that signed the cert, as a string.
148 	 * @since 4.0
149 	 */
150 	public String getPusher() {
151 		return pusher.getRaw();
152 	}
153 
154 	/**
155 	 * Get identity of the pusher who signed the cert.
156 	 *
157 	 * @return identity of the pusher who signed the cert.
158 	 * @since 4.1
159 	 */
160 	public PushCertificateIdent getPusherIdent() {
161 		return pusher;
162 	}
163 
164 	/**
165 	 * Get URL of the repository the push was originally sent to.
166 	 *
167 	 * @return URL of the repository the push was originally sent to.
168 	 * @since 4.0
169 	 */
170 	public String getPushee() {
171 		return pushee;
172 	}
173 
174 	/**
175 	 * Get the raw nonce value that was presented by the pusher.
176 	 *
177 	 * @return the raw nonce value that was presented by the pusher.
178 	 * @since 4.1
179 	 */
180 	public String getNonce() {
181 		return nonce;
182 	}
183 
184 	/**
185 	 * Get verification status of the nonce embedded in the certificate.
186 	 *
187 	 * @return verification status of the nonce embedded in the certificate.
188 	 * @since 4.0
189 	 */
190 	public NonceStatus getNonceStatus() {
191 		return nonceStatus;
192 	}
193 
194 	/**
195 	 * Get the list of commands as one string to be feed into the signature
196 	 * verifier.
197 	 *
198 	 * @return the list of commands as one string to be feed into the signature
199 	 *         verifier.
200 	 * @since 4.1
201 	 */
202 	public List<ReceiveCommand> getCommands() {
203 		return commands;
204 	}
205 
206 	/**
207 	 * Get the raw signature
208 	 *
209 	 * @return the raw signature, consisting of the lines received between the
210 	 *         lines {@code "----BEGIN GPG SIGNATURE-----\n"} and
211 	 *         {@code "----END GPG SIGNATURE-----\n}", inclusive.
212 	 * @since 4.0
213 	 */
214 	public String getSignature() {
215 		return signature;
216 	}
217 
218 	/**
219 	 * Get text payload of the certificate for the signature verifier.
220 	 *
221 	 * @return text payload of the certificate for the signature verifier.
222 	 * @since 4.1
223 	 */
224 	public String toText() {
225 		return toStringBuilder().toString();
226 	}
227 
228 	/**
229 	 * Get original text payload plus signature
230 	 *
231 	 * @return original text payload plus signature; the final output will be
232 	 *         valid as input to
233 	 *         {@link org.eclipse.jgit.transport.PushCertificateParser#fromString(String)}.
234 	 * @since 4.1
235 	 */
236 	public String toTextWithSignature() {
237 		return toStringBuilder().append(signature).toString();
238 	}
239 
240 	private StringBuilder toStringBuilder() {
241 		StringBuilder sb = new StringBuilder()
242 				.append(VERSION).append(' ').append(version).append('\n')
243 				.append(PUSHER).append(' ').append(getPusher())
244 				.append('\n');
245 		if (pushee != null) {
246 			sb.append(PUSHEE).append(' ').append(pushee).append('\n');
247 		}
248 		sb.append(NONCE).append(' ').append(nonce).append('\n')
249 				.append('\n');
250 		for (ReceiveCommand cmd : commands) {
251 			sb.append(cmd.getOldId().name())
252 				.append(' ').append(cmd.getNewId().name())
253 				.append(' ').append(cmd.getRefName()).append('\n');
254 		}
255 		return sb;
256 	}
257 
258 	/** {@inheritDoc} */
259 	@Override
260 	public int hashCode() {
261 		return signature.hashCode();
262 	}
263 
264 	/** {@inheritDoc} */
265 	@Override
266 	public boolean equals(Object o) {
267 		if (!(o instanceof PushCertificate)) {
268 			return false;
269 		}
270 		PushCertificate p = (PushCertificate) o;
271 		return version.equals(p.version)
272 				&& pusher.equals(p.pusher)
273 				&& Objects.equals(pushee, p.pushee)
274 				&& nonceStatus == p.nonceStatus
275 				&& signature.equals(p.signature)
276 				&& commandsEqual(this, p);
277 	}
278 
279 	private static boolean commandsEqual(PushCertificate c1, PushCertificate c2) {
280 		if (c1.commands.size() != c2.commands.size()) {
281 			return false;
282 		}
283 		for (int i = 0; i < c1.commands.size(); i++) {
284 			ReceiveCommand cmd1 = c1.commands.get(i);
285 			ReceiveCommand cmd2 = c2.commands.get(i);
286 			if (!cmd1.getOldId().equals(cmd2.getOldId())
287 					|| !cmd1.getNewId().equals(cmd2.getNewId())
288 					|| !cmd1.getRefName().equals(cmd2.getRefName())) {
289 				return false;
290 			}
291 		}
292 		return true;
293 	}
294 
295 	/** {@inheritDoc} */
296 	@Override
297 	public String toString() {
298 		return getClass().getSimpleName() + '['
299 				 + toTextWithSignature() + ']';
300 	}
301 }