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