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 public static PushCertificate fromReader(Reader r)
155 throws PackProtocolException, IOException {
156 return new PushCertificateParser().parse(r);
157 }
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 public static PushCertificate fromString(String str)
173 throws PackProtocolException, IOException {
174 return fromReader(new java.io.StringReader(str));
175 }
176
177 private boolean received;
178 private String version;
179 private PushCertificateIdent pusher;
180 private String pushee;
181
182
183 private String sentNonce;
184
185
186
187
188
189
190
191 private String receivedNonce;
192
193 private NonceStatus nonceStatus;
194 private String signature;
195
196
197 private final Repository db;
198
199
200
201
202
203 private final int nonceSlopLimit;
204
205 private final boolean enabled;
206 private final NonceGenerator nonceGenerator;
207 private final List<ReceiveCommand> commands = new ArrayList<>();
208
209
210
211
212
213
214
215
216 public PushCertificateParser(Repository into, SignedPushConfig cfg) {
217 if (cfg != null) {
218 nonceSlopLimit = cfg.getCertNonceSlopLimit();
219 nonceGenerator = cfg.getNonceGenerator();
220 } else {
221 nonceSlopLimit = 0;
222 nonceGenerator = null;
223 }
224 db = into;
225 enabled = nonceGenerator != null;
226 }
227
228 private PushCertificateParser() {
229 db = null;
230 nonceSlopLimit = 0;
231 nonceGenerator = null;
232 enabled = true;
233 }
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 public PushCertificate parse(Reader r)
250 throws PackProtocolException, IOException {
251 StreamReader reader = new StreamReader(r);
252 receiveHeader(reader, true);
253 String line;
254 try {
255 while (!(line = reader.read()).isEmpty()) {
256 if (line.equals(BEGIN_SIGNATURE)) {
257 receiveSignature(reader);
258 break;
259 }
260 addCommand(line);
261 }
262 } catch (EOFException e) {
263
264
265 }
266 return build();
267 }
268
269
270
271
272
273
274
275 public PushCertificate build() throws IOException {
276 if (!received || !enabled) {
277 return null;
278 }
279 try {
280 return new PushCertificate(version, pusher, pushee, receivedNonce,
281 nonceStatus, Collections.unmodifiableList(commands), signature);
282 } catch (IllegalArgumentException e) {
283 throw new IOException(e.getMessage(), e);
284 }
285 }
286
287
288
289
290
291
292 public boolean enabled() {
293 return enabled;
294 }
295
296
297
298
299
300
301 public String getAdvertiseNonce() {
302 String nonce = sentNonce();
303 if (nonce == null) {
304 return null;
305 }
306 return CAPABILITY_PUSH_CERT + '=' + nonce;
307 }
308
309 private String sentNonce() {
310 if (sentNonce == null && nonceGenerator != null) {
311 sentNonce = nonceGenerator.createNonce(db,
312 TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
313 }
314 return sentNonce;
315 }
316
317 private static String parseHeader(StringReader reader, String header)
318 throws IOException {
319 return parseHeader(reader.read(), header);
320 }
321
322 private static String parseHeader(String s, String header)
323 throws IOException {
324 if (s.isEmpty()) {
325 throw new EOFException();
326 }
327 if (s.length() <= header.length()
328 || !s.startsWith(header)
329 || s.charAt(header.length()) != ' ') {
330 throw new PackProtocolException(MessageFormat.format(
331 JGitText.get().pushCertificateInvalidField, header));
332 }
333 return s.substring(header.length() + 1);
334 }
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356 public void receiveHeader(PacketLineIn pckIn, boolean stateless)
357 throws IOException {
358 receiveHeader(new PacketLineReader(pckIn), stateless);
359 }
360
361 private void receiveHeader(StringReader reader, boolean stateless)
362 throws IOException {
363 try {
364 try {
365 version = parseHeader(reader, VERSION);
366 } catch (EOFException e) {
367 return;
368 }
369 received = true;
370 if (!version.equals(VERSION_0_1)) {
371 throw new PackProtocolException(MessageFormat.format(
372 JGitText.get().pushCertificateInvalidFieldValue, VERSION, version));
373 }
374 String rawPusher = parseHeader(reader, PUSHER);
375 pusher = PushCertificateIdent.parse(rawPusher);
376 if (pusher == null) {
377 throw new PackProtocolException(MessageFormat.format(
378 JGitText.get().pushCertificateInvalidFieldValue,
379 PUSHER, rawPusher));
380 }
381 String next = reader.read();
382 if (next.startsWith(PUSHEE)) {
383 pushee = parseHeader(next, PUSHEE);
384 receivedNonce = parseHeader(reader, NONCE);
385 } else {
386 receivedNonce = parseHeader(next, NONCE);
387 }
388 nonceStatus = nonceGenerator != null
389 ? nonceGenerator.verify(
390 receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
391 : NonceStatus.UNSOLICITED;
392
393 if (!reader.read().isEmpty()) {
394 throw new PackProtocolException(
395 JGitText.get().pushCertificateInvalidHeader);
396 }
397 } catch (EOFException eof) {
398 throw new PackProtocolException(
399 JGitText.get().pushCertificateInvalidHeader, eof);
400 }
401 }
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 public void receiveSignature(PacketLineIn pckIn) throws IOException {
418 StringReader reader = new PacketLineReader(pckIn);
419 receiveSignature(reader);
420 if (!reader.read().equals(END_CERT)) {
421 throw new PackProtocolException(
422 JGitText.get().pushCertificateInvalidSignature);
423 }
424 }
425
426 private void receiveSignature(StringReader reader) throws IOException {
427 received = true;
428 try {
429 StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n');
430 String line;
431 while (!(line = reader.read()).equals(END_SIGNATURE)) {
432 sig.append(line).append('\n');
433 }
434 signature = sig.append(END_SIGNATURE).append('\n').toString();
435 } catch (EOFException eof) {
436 throw new PackProtocolException(
437 JGitText.get().pushCertificateInvalidSignature, eof);
438 }
439 }
440
441
442
443
444
445
446
447
448 public void addCommand(ReceiveCommand cmd) {
449 commands.add(cmd);
450 }
451
452
453
454
455
456
457
458
459
460
461
462 public void addCommand(String line) throws PackProtocolException {
463 commands.add(parseCommand(line));
464 }
465 }