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