1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.transport;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
14 import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
15 import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
16
17 import java.io.IOException;
18 import java.net.URL;
19 import java.security.MessageDigest;
20 import java.security.NoSuchAlgorithmException;
21 import java.security.SecureRandom;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Map.Entry;
30
31 import org.eclipse.jgit.transport.http.HttpConnection;
32 import org.eclipse.jgit.util.Base64;
33 import org.eclipse.jgit.util.GSSManagerFactory;
34 import org.ietf.jgss.GSSContext;
35 import org.ietf.jgss.GSSException;
36 import org.ietf.jgss.GSSManager;
37 import org.ietf.jgss.GSSName;
38 import org.ietf.jgss.Oid;
39
40
41
42
43
44
45
46 abstract class HttpAuthMethod {
47
48
49
50
51 public enum Type {
52 NONE {
53 @Override
54 public HttpAuthMethod method(String hdr) {
55 return None.INSTANCE;
56 }
57
58 @Override
59 public String getSchemeName() {
60 return "None";
61 }
62 },
63 BASIC {
64 @Override
65 public HttpAuthMethod method(String hdr) {
66 return new Basic();
67 }
68
69 @Override
70 public String getSchemeName() {
71 return "Basic";
72 }
73 },
74 DIGEST {
75 @Override
76 public HttpAuthMethod method(String hdr) {
77 return new Digest(hdr);
78 }
79
80 @Override
81 public String getSchemeName() {
82 return "Digest";
83 }
84 },
85 NEGOTIATE {
86 @Override
87 public HttpAuthMethod method(String hdr) {
88 return new Negotiate(hdr);
89 }
90
91 @Override
92 public String getSchemeName() {
93 return "Negotiate";
94 }
95 };
96
97
98
99
100
101
102
103 public abstract HttpAuthMethod method(String hdr);
104
105
106
107
108
109
110 public abstract String getSchemeName();
111 }
112
113 static final String EMPTY_STRING = "";
114 static final String SCHEMA_NAME_SEPARATOR = " ";
115
116
117
118
119
120
121
122
123
124
125 static HttpAuthMethod scanResponse(final HttpConnection conn,
126 Collection<Type> ignoreTypes) {
127 final Map<String, List<String>> headers = conn.getHeaderFields();
128 HttpAuthMethod authentication = Type.NONE.method(EMPTY_STRING);
129
130 for (Entry<String, List<String>> entry : headers.entrySet()) {
131 if (HDR_WWW_AUTHENTICATE.equalsIgnoreCase(entry.getKey())) {
132 if (entry.getValue() != null) {
133 for (String value : entry.getValue()) {
134 if (value != null && value.length() != 0) {
135 final String[] valuePart = value.split(
136 SCHEMA_NAME_SEPARATOR, 2);
137
138 try {
139 Type methodType = Type.valueOf(
140 valuePart[0].toUpperCase(Locale.ROOT));
141
142 if ((ignoreTypes != null)
143 && (ignoreTypes.contains(methodType))) {
144 continue;
145 }
146
147 if (authentication.getType().compareTo(methodType) >= 0) {
148 continue;
149 }
150
151 final String param;
152 if (valuePart.length == 1)
153 param = EMPTY_STRING;
154 else
155 param = valuePart[1];
156
157 authentication = methodType
158 .method(param);
159 } catch (IllegalArgumentException e) {
160
161 }
162 }
163 }
164 }
165 break;
166 }
167 }
168
169 return authentication;
170 }
171
172 protected final Type type;
173
174
175
176
177
178
179
180 protected HttpAuthMethod(Type type) {
181 this.type = type;
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196 boolean authorize(URIish uri, CredentialsProvider credentialsProvider) {
197 String username;
198 String password;
199
200 if (credentialsProvider != null) {
201 CredentialItem.Username u = new CredentialItem.Username();
202 CredentialItem.Password p = new CredentialItem.Password();
203
204 if (credentialsProvider.supports(u, p)
205 && credentialsProvider.get(uri, u, p)) {
206 username = u.getValue();
207 char[] v = p.getValue();
208 password = (v == null) ? null : new String(p.getValue());
209 p.clear();
210 } else
211 return false;
212 } else {
213 username = uri.getUser();
214 password = uri.getPass();
215 }
216 if (username != null) {
217 authorize(username, password);
218 return true;
219 }
220 return false;
221 }
222
223
224
225
226
227
228
229 abstract void authorize(String user, String pass);
230
231
232
233
234
235
236
237 abstract void configureRequest(HttpConnection conn) throws IOException;
238
239
240
241
242
243
244 public Type getType() {
245 return type;
246 }
247
248
249 private static class None extends HttpAuthMethod {
250 static final None INSTANCE = new None();
251 public None() {
252 super(Type.NONE);
253 }
254
255 @Override
256 void authorize(String user, String pass) {
257
258 }
259
260 @Override
261 void configureRequest(HttpConnection conn) throws IOException {
262
263 }
264 }
265
266
267 private static class Basic extends HttpAuthMethod {
268 private String user;
269
270 private String pass;
271
272 public Basic() {
273 super(Type.BASIC);
274 }
275
276 @Override
277 void authorize(String username, String password) {
278 this.user = username;
279 this.pass = password;
280 }
281
282 @Override
283 void configureRequest(HttpConnection conn) throws IOException {
284 String ident = user + ":" + pass;
285 String enc = Base64.encodeBytes(ident.getBytes(UTF_8));
286 conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName()
287 + " " + enc);
288 }
289 }
290
291
292 private static class Digest extends HttpAuthMethod {
293 private static final SecureRandom PRNG = new SecureRandom();
294
295 private final Map<String, String> params;
296
297 private int requestCount;
298
299 private String user;
300
301 private String pass;
302
303 Digest(String hdr) {
304 super(Type.DIGEST);
305 params = parse(hdr);
306
307 final String qop = params.get("qop");
308 if ("auth".equals(qop)) {
309 final byte[] bin = new byte[8];
310 PRNG.nextBytes(bin);
311 params.put("cnonce", Base64.encodeBytes(bin));
312 }
313 }
314
315 @Override
316 void authorize(String username, String password) {
317 this.user = username;
318 this.pass = password;
319 }
320
321 @SuppressWarnings("boxing")
322 @Override
323 void configureRequest(HttpConnection conn) throws IOException {
324 final Map<String, String> r = new LinkedHashMap<>();
325
326 final String realm = params.get("realm");
327 final String nonce = params.get("nonce");
328 final String cnonce = params.get("cnonce");
329 final String uri = uri(conn.getURL());
330 final String qop = params.get("qop");
331 final String method = conn.getRequestMethod();
332
333 final String A1 = user + ":" + realm + ":" + pass;
334 final String A2 = method + ":" + uri;
335
336 r.put("username", user);
337 r.put("realm", realm);
338 r.put("nonce", nonce);
339 r.put("uri", uri);
340
341 final String response, nc;
342 if ("auth".equals(qop)) {
343 nc = String.format("%08x", ++requestCount);
344 response = KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":"
345 + qop + ":"
346 + H(A2));
347 } else {
348 nc = null;
349 response = KD(H(A1), nonce + ":" + H(A2));
350 }
351 r.put("response", response);
352 if (params.containsKey("algorithm"))
353 r.put("algorithm", "MD5");
354 if (cnonce != null && qop != null)
355 r.put("cnonce", cnonce);
356 if (params.containsKey("opaque"))
357 r.put("opaque", params.get("opaque"));
358 if (qop != null)
359 r.put("qop", qop);
360 if (nc != null)
361 r.put("nc", nc);
362
363 StringBuilder v = new StringBuilder();
364 for (Map.Entry<String, String> e : r.entrySet()) {
365 if (v.length() > 0)
366 v.append(", ");
367 v.append(e.getKey());
368 v.append('=');
369 v.append('"');
370 v.append(e.getValue());
371 v.append('"');
372 }
373 conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName()
374 + " " + v);
375 }
376
377 private static String uri(URL u) {
378 StringBuilder r = new StringBuilder();
379 r.append(u.getProtocol());
380 r.append("://"); //$NON-NLS-1$
381 r.append(u.getHost());
382 if (0 < u.getPort()) {
383 if (u.getPort() == 80 && "http".equals(u.getProtocol())) {
384
385 } else if (u.getPort() == 443
386 && "https".equals(u.getProtocol())) {
387
388 } else {
389 r.append(':').append(u.getPort());
390 }
391 }
392 r.append(u.getPath());
393 if (u.getQuery() != null)
394 r.append('?').append(u.getQuery());
395 return r.toString();
396 }
397
398 private static String H(String data) {
399 MessageDigest md = newMD5();
400 md.update(data.getBytes(UTF_8));
401 return LHEX(md.digest());
402 }
403
404 private static String KD(String secret, String data) {
405 MessageDigest md = newMD5();
406 md.update(secret.getBytes(UTF_8));
407 md.update((byte) ':');
408 md.update(data.getBytes(UTF_8));
409 return LHEX(md.digest());
410 }
411
412 private static MessageDigest newMD5() {
413 try {
414 return MessageDigest.getInstance("MD5");
415 } catch (NoSuchAlgorithmException e) {
416 throw new RuntimeException("No MD5 available", e);
417 }
418 }
419
420 private static final char[] LHEX = { '0', '1', '2', '3', '4', '5', '6',
421 '7', '8', '9',
422 'a', 'b', 'c', 'd', 'e', 'f' };
423
424 private static String LHEX(byte[] bin) {
425 StringBuilder r = new StringBuilder(bin.length * 2);
426 for (byte b : bin) {
427 r.append(LHEX[(b >>> 4) & 0x0f]);
428 r.append(LHEX[b & 0x0f]);
429 }
430 return r.toString();
431 }
432
433 private static Map<String, String> parse(String auth) {
434 Map<String, String> p = new HashMap<>();
435 int next = 0;
436 while (next < auth.length()) {
437 if (next < auth.length() && auth.charAt(next) == ',') {
438 next++;
439 }
440 while (next < auth.length()
441 && Character.isWhitespace(auth.charAt(next))) {
442 next++;
443 }
444
445 int eq = auth.indexOf('=', next);
446 if (eq < 0 || eq + 1 == auth.length()) {
447 return Collections.emptyMap();
448 }
449
450 final String name = auth.substring(next, eq);
451 final String value;
452 if (auth.charAt(eq + 1) == '"') {
453 int dq = auth.indexOf('"', eq + 2);
454 if (dq < 0) {
455 return Collections.emptyMap();
456 }
457 value = auth.substring(eq + 2, dq);
458 next = dq + 1;
459
460 } else {
461 int space = auth.indexOf(' ', eq + 1);
462 int comma = auth.indexOf(',', eq + 1);
463 if (space < 0)
464 space = auth.length();
465 if (comma < 0)
466 comma = auth.length();
467
468 final int e = Math.min(space, comma);
469 value = auth.substring(eq + 1, e);
470 next = e + 1;
471 }
472 p.put(name, value);
473 }
474 return p;
475 }
476 }
477
478 private static class Negotiate extends HttpAuthMethod {
479 private static final GSSManagerFactory GSS_MANAGER_FACTORY = GSSManagerFactory
480 .detect();
481
482 private static final Oid OID;
483 static {
484 try {
485
486 OID = new Oid("1.3.6.1.5.5.2");
487 } catch (GSSException e) {
488 throw new Error("Cannot create NEGOTIATE oid.", e);
489 }
490 }
491
492 private final byte[] prevToken;
493
494 public Negotiate(String hdr) {
495 super(Type.NEGOTIATE);
496 prevToken = Base64.decode(hdr);
497 }
498
499 @Override
500 void authorize(String user, String pass) {
501
502 }
503
504 @Override
505 void configureRequest(HttpConnection conn) throws IOException {
506 GSSManager gssManager = GSS_MANAGER_FACTORY.newInstance(conn
507 .getURL());
508 String host = conn.getURL().getHost();
509 String peerName = "HTTP@" + host.toLowerCase(Locale.ROOT);
510 try {
511 GSSName gssName = gssManager.createName(peerName,
512 GSSName.NT_HOSTBASED_SERVICE);
513 GSSContext context = gssManager.createContext(gssName, OID,
514 null, GSSContext.DEFAULT_LIFETIME);
515
516 context.requestCredDeleg(true);
517
518 byte[] token = context.initSecContext(prevToken, 0,
519 prevToken.length);
520
521 conn.setRequestProperty(HDR_AUTHORIZATION, getType().getSchemeName()
522 + " " + Base64.encodeBytes(token));
523 } catch (GSSException e) {
524 throw new IOException(e);
525 }
526 }
527 }
528 }