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 package org.eclipse.jgit.transport;
44
45 import static java.nio.charset.StandardCharsets.ISO_8859_1;
46 import static java.nio.charset.StandardCharsets.UTF_8;
47
48 import java.io.File;
49 import java.security.InvalidKeyException;
50 import java.security.NoSuchAlgorithmException;
51
52 import javax.crypto.Mac;
53 import javax.crypto.spec.SecretKeySpec;
54
55 import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
56 import org.eclipse.jgit.lib.Repository;
57 import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
58
59
60
61
62
63
64 public class HMACSHA1NonceGenerator implements NonceGenerator {
65
66 private Mac mac;
67
68
69
70
71
72
73
74
75 public HMACSHA1NonceGenerator(String seed) throws IllegalStateException {
76 try {
77 byte[] keyBytes = seed.getBytes(ISO_8859_1);
78 SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
79 mac = Mac.getInstance("HmacSHA1");
80 mac.init(signingKey);
81 } catch (InvalidKeyException e) {
82 throw new IllegalStateException(e);
83 } catch (NoSuchAlgorithmException e) {
84 throw new IllegalStateException(e);
85 }
86 }
87
88
89 @Override
90 public synchronized String createNonce(Repository repo, long timestamp)
91 throws IllegalStateException {
92 String path;
93 if (repo instanceof DfsRepository) {
94 path = ((DfsRepository) repo).getDescription().getRepositoryName();
95 } else {
96 File directory = repo.getDirectory();
97 if (directory != null) {
98 path = directory.getPath();
99 } else {
100 throw new IllegalStateException();
101 }
102 }
103
104 String input = path + ":" + String.valueOf(timestamp);
105 byte[] rawHmac = mac.doFinal(input.getBytes(UTF_8));
106 return Long.toString(timestamp) + "-" + toHex(rawHmac);
107 }
108
109
110 @Override
111 public NonceStatus verify(String received, String sent,
112 Repository db, boolean allowSlop, int slop) {
113 if (received.isEmpty()) {
114 return NonceStatus.MISSING;
115 } else if (sent.isEmpty()) {
116 return NonceStatus.UNSOLICITED;
117 } else if (received.equals(sent)) {
118 return NonceStatus.OK;
119 }
120
121 if (!allowSlop) {
122 return NonceStatus.BAD;
123 }
124
125
126 int idxSent = sent.indexOf('-');
127 int idxRecv = received.indexOf('-');
128 if (idxSent == -1 || idxRecv == -1) {
129 return NonceStatus.BAD;
130 }
131
132 String signedStampStr = received.substring(0, idxRecv);
133 String advertisedStampStr = sent.substring(0, idxSent);
134 long signedStamp;
135 long advertisedStamp;
136 try {
137 signedStamp = Long.parseLong(signedStampStr);
138 advertisedStamp = Long.parseLong(advertisedStampStr);
139 } catch (IllegalArgumentException e) {
140 return NonceStatus.BAD;
141 }
142
143
144 String expect = createNonce(db, signedStamp);
145
146 if (!expect.equals(received)) {
147 return NonceStatus.BAD;
148 }
149
150 long nonceStampSlop = Math.abs(advertisedStamp - signedStamp);
151
152 if (nonceStampSlop <= slop) {
153 return NonceStatus.OK;
154 } else {
155 return NonceStatus.SLOP;
156 }
157 }
158
159 private static final String HEX = "0123456789ABCDEF";
160
161 private static String toHex(byte[] bytes) {
162 StringBuilder builder = new StringBuilder(2 * bytes.length);
163 for (byte b : bytes) {
164 builder.append(HEX.charAt((b & 0xF0) >> 4));
165 builder.append(HEX.charAt(b & 0xF));
166 }
167 return builder.toString();
168 }
169 }