1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.transport;
12
13 import static org.eclipse.jgit.transport.ReceivePack.parseCommand;
14 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT;
15
16 import java.io.EOFException;
17 import java.io.IOException;
18 import java.io.Reader;
19 import java.text.MessageFormat;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.jgit.errors.PackProtocolException;
26 import org.eclipse.jgit.internal.JGitText;
27 import org.eclipse.jgit.lib.Repository;
28 import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
29 import org.eclipse.jgit.util.IO;
30
31
32
33
34
35
36 public class PushCertificateParser {
37 static final String BEGIN_SIGNATURE =
38 "-----BEGIN PGP SIGNATURE-----";
39 static final String END_SIGNATURE =
40 "-----END PGP SIGNATURE-----";
41
42 static final String VERSION = "certificate version";
43
44 static final String PUSHER = "pusher";
45
46 static final String PUSHEE = "pushee";
47
48 static final String NONCE = "nonce";
49
50 static final String END_CERT = "push-cert-end";
51
52 private static final String VERSION_0_1 = "0.1";
53
54 private static interface StringReader {
55
56
57
58
59
60
61
62
63
64 String read() throws EOFException, IOException;
65 }
66
67 private static class PacketLineReader implements StringReader {
68 private final PacketLineIn pckIn;
69
70 private PacketLineReader(PacketLineIn pckIn) {
71 this.pckIn = pckIn;
72 }
73
74 @Override
75 public String read() throws IOException {
76 return pckIn.readString();
77 }
78 }
79
80 private static class StreamReader implements StringReader {
81 private final Reader reader;
82
83 private StreamReader(Reader reader) {
84 this.reader = reader;
85 }
86
87 @Override
88 public String read() throws IOException {
89
90 String line = IO.readLine(reader, 41 * 2 + 64);
91 if (line.isEmpty()) {
92 throw new EOFException();
93 } else if (line.charAt(line.length() - 1) == '\n') {
94 line = line.substring(0, line.length() - 1);
95 }
96 return line;
97 }
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122 public static PushCertificate fromReader(Reader r)
123 throws PackProtocolException, IOException {
124 return new PushCertificateParser().parse(r);
125 }
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140 public static PushCertificate fromString(String str)
141 throws PackProtocolException, IOException {
142 return fromReader(new java.io.StringReader(str));
143 }
144
145 private boolean received;
146 private String version;
147 private PushCertificateIdent pusher;
148 private String pushee;
149
150
151 private String sentNonce;
152
153
154
155
156
157
158
159 private String receivedNonce;
160
161 private NonceStatus nonceStatus;
162 private String signature;
163
164
165 private final Repository db;
166
167
168
169
170
171 private final int nonceSlopLimit;
172
173 private final boolean enabled;
174 private final NonceGenerator nonceGenerator;
175 private final List<ReceiveCommand> commands = new ArrayList<>();
176
177
178
179
180
181
182
183
184
185
186 public PushCertificateParser(Repository into, SignedPushConfig cfg) {
187 if (cfg != null) {
188 nonceSlopLimit = cfg.getCertNonceSlopLimit();
189 nonceGenerator = cfg.getNonceGenerator();
190 } else {
191 nonceSlopLimit = 0;
192 nonceGenerator = null;
193 }
194 db = into;
195 enabled = nonceGenerator != null;
196 }
197
198 private PushCertificateParser() {
199 db = null;
200 nonceSlopLimit = 0;
201 nonceGenerator = null;
202 enabled = true;
203 }
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219 public PushCertificate parse(Reader r)
220 throws PackProtocolException, IOException {
221 StreamReader reader = new StreamReader(r);
222 receiveHeader(reader, true);
223 String line;
224 try {
225 while (!(line = reader.read()).isEmpty()) {
226 if (line.equals(BEGIN_SIGNATURE)) {
227 receiveSignature(reader);
228 break;
229 }
230 addCommand(line);
231 }
232 } catch (EOFException e) {
233
234
235 }
236 return build();
237 }
238
239
240
241
242
243
244
245
246
247
248 public PushCertificate build() throws IOException {
249 if (!received || !enabled) {
250 return null;
251 }
252 try {
253 return new PushCertificate(version, pusher, pushee, receivedNonce,
254 nonceStatus, Collections.unmodifiableList(commands), signature);
255 } catch (IllegalArgumentException e) {
256 throw new IOException(e.getMessage(), e);
257 }
258 }
259
260
261
262
263
264
265
266
267
268 public boolean enabled() {
269 return enabled;
270 }
271
272
273
274
275
276
277
278
279
280 public String getAdvertiseNonce() {
281 String nonce = sentNonce();
282 if (nonce == null) {
283 return null;
284 }
285 return CAPABILITY_PUSH_CERT + '=' + nonce;
286 }
287
288 private String sentNonce() {
289 if (sentNonce == null && nonceGenerator != null) {
290 sentNonce = nonceGenerator.createNonce(db,
291 TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
292 }
293 return sentNonce;
294 }
295
296 private static String parseHeader(StringReader reader, String header)
297 throws IOException {
298 return parseHeader(reader.read(), header);
299 }
300
301 private static String parseHeader(String s, String header)
302 throws IOException {
303 if (s.isEmpty()) {
304 throw new EOFException();
305 }
306 if (s.length() <= header.length()
307 || !s.startsWith(header)
308 || s.charAt(header.length()) != ' ') {
309 throw new PackProtocolException(MessageFormat.format(
310 JGitText.get().pushCertificateInvalidField, header));
311 }
312 return s.substring(header.length() + 1);
313 }
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335 public void receiveHeader(PacketLineIn pckIn, boolean stateless)
336 throws IOException {
337 receiveHeader(new PacketLineReader(pckIn), stateless);
338 }
339
340 private void receiveHeader(StringReader reader, boolean stateless)
341 throws IOException {
342 try {
343 try {
344 version = parseHeader(reader, VERSION);
345 } catch (EOFException e) {
346 return;
347 }
348 received = true;
349 if (!version.equals(VERSION_0_1)) {
350 throw new PackProtocolException(MessageFormat.format(
351 JGitText.get().pushCertificateInvalidFieldValue, VERSION, version));
352 }
353 String rawPusher = parseHeader(reader, PUSHER);
354 pusher = PushCertificateIdent.parse(rawPusher);
355 if (pusher == null) {
356 throw new PackProtocolException(MessageFormat.format(
357 JGitText.get().pushCertificateInvalidFieldValue,
358 PUSHER, rawPusher));
359 }
360 String next = reader.read();
361 if (next.startsWith(PUSHEE)) {
362 pushee = parseHeader(next, PUSHEE);
363 receivedNonce = parseHeader(reader, NONCE);
364 } else {
365 receivedNonce = parseHeader(next, NONCE);
366 }
367 nonceStatus = nonceGenerator != null
368 ? nonceGenerator.verify(
369 receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
370 : NonceStatus.UNSOLICITED;
371
372 if (!reader.read().isEmpty()) {
373 throw new PackProtocolException(
374 JGitText.get().pushCertificateInvalidHeader);
375 }
376 } catch (EOFException eof) {
377 throw new PackProtocolException(
378 JGitText.get().pushCertificateInvalidHeader, eof);
379 }
380 }
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396 public void receiveSignature(PacketLineIn pckIn) throws IOException {
397 StringReader reader = new PacketLineReader(pckIn);
398 receiveSignature(reader);
399 if (!reader.read().equals(END_CERT)) {
400 throw new PackProtocolException(
401 JGitText.get().pushCertificateInvalidSignature);
402 }
403 }
404
405 private void receiveSignature(StringReader reader) throws IOException {
406 received = true;
407 try {
408 StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n');
409 String line;
410 while (!(line = reader.read()).equals(END_SIGNATURE)) {
411 sig.append(line).append('\n');
412 }
413 signature = sig.append(END_SIGNATURE).append('\n').toString();
414 } catch (EOFException eof) {
415 throw new PackProtocolException(
416 JGitText.get().pushCertificateInvalidSignature, eof);
417 }
418 }
419
420
421
422
423
424
425
426
427 public void addCommand(ReceiveCommand cmd) {
428 commands.add(cmd);
429 }
430
431
432
433
434
435
436
437
438
439
440
441 public void addCommand(String line) throws PackProtocolException {
442 commands.add(parseCommand(line));
443 }
444 }