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 	 * @return the certificate version string.
136 	 * @since 4.1
137 	 */
138 	public String getVersion() {
139 		return version;
140 	}
141 
142 	/**
143 	 * @return the raw line that signed the cert, as a string.
144 	 * @since 4.0
145 	 */
146 	public String getPusher() {
147 		return pusher.getRaw();
148 	}
149 
150 	/**
151 	 * @return identity of the pusher who signed the cert.
152 	 * @since 4.1
153 	 */
154 	public PushCertificateIdent getPusherIdent() {
155 		return pusher;
156 	}
157 
158 	/**
159 	 * @return URL of the repository the push was originally sent to.
160 	 * @since 4.0
161 	 */
162 	public String getPushee() {
163 		return pushee;
164 	}
165 
166 	/**
167 	 * @return the raw nonce value that was presented by the pusher.
168 	 * @since 4.1
169 	 */
170 	public String getNonce() {
171 		return nonce;
172 	}
173 
174 	/**
175 	 * @return verification status of the nonce embedded in the certificate.
176 	 * @since 4.0
177 	 */
178 	public NonceStatus getNonceStatus() {
179 		return nonceStatus;
180 	}
181 
182 	/**
183 	 * @return the list of commands as one string to be feed into the signature
184 	 *         verifier.
185 	 * @since 4.1
186 	 */
187 	public List<ReceiveCommand> getCommands() {
188 		return commands;
189 	}
190 
191 	/**
192 	 * @return the raw signature, consisting of the lines received between the
193 	 *     lines {@code "----BEGIN GPG SIGNATURE-----\n"} and
194 	 *     {@code "----END GPG SIGNATURE-----\n}", inclusive.
195 	 * @since 4.0
196 	 */
197 	public String getSignature() {
198 		return signature;
199 	}
200 
201 	/**
202 	 * @return text payload of the certificate for the signature verifier.
203 	 * @since 4.1
204 	 */
205 	public String toText() {
206 		return toStringBuilder().toString();
207 	}
208 
209 	/**
210 	 * @return original text payload plus signature; the final output will be
211 	 *     valid as input to {@link PushCertificateParser#fromString(String)}.
212 	 * @since 4.1
213 	 */
214 	public String toTextWithSignature() {
215 		return toStringBuilder().append(signature).toString();
216 	}
217 
218 	private StringBuilder toStringBuilder() {
219 		StringBuilder sb = new StringBuilder()
220 				.append(VERSION).append(' ').append(version).append('\n')
221 				.append(PUSHER).append(' ').append(getPusher())
222 				.append('\n');
223 		if (pushee != null) {
224 			sb.append(PUSHEE).append(' ').append(pushee).append('\n');
225 		}
226 		sb.append(NONCE).append(' ').append(nonce).append('\n')
227 				.append('\n');
228 		for (ReceiveCommand cmd : commands) {
229 			sb.append(cmd.getOldId().name())
230 				.append(' ').append(cmd.getNewId().name())
231 				.append(' ').append(cmd.getRefName()).append('\n');
232 		}
233 		return sb;
234 	}
235 
236 	@Override
237 	public int hashCode() {
238 		return signature.hashCode();
239 	}
240 
241 	@Override
242 	public boolean equals(Object o) {
243 		if (!(o instanceof PushCertificate)) {
244 			return false;
245 		}
246 		PushCertificate p = (PushCertificate) o;
247 		return version.equals(p.version)
248 				&& pusher.equals(p.pusher)
249 				&& Objects.equals(pushee, p.pushee)
250 				&& nonceStatus == p.nonceStatus
251 				&& signature.equals(p.signature)
252 				&& commandsEqual(this, p);
253 	}
254 
255 	private static boolean commandsEqual(PushCertificate c1, PushCertificate c2) {
256 		if (c1.commands.size() != c2.commands.size()) {
257 			return false;
258 		}
259 		for (int i = 0; i < c1.commands.size(); i++) {
260 			ReceiveCommand cmd1 = c1.commands.get(i);
261 			ReceiveCommand cmd2 = c2.commands.get(i);
262 			if (!cmd1.getOldId().equals(cmd2.getOldId())
263 					|| !cmd1.getNewId().equals(cmd2.getNewId())
264 					|| !cmd1.getRefName().equals(cmd2.getRefName())) {
265 				return false;
266 			}
267 		}
268 		return true;
269 	}
270 
271 	@Override
272 	public String toString() {
273 		return getClass().getSimpleName() + '['
274 				 + toTextWithSignature() + ']';
275 	}
276 }