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