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.util.HttpSupport.HDR_AUTHORIZATION;
47 import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
48
49 import java.io.IOException;
50 import java.io.UnsupportedEncodingException;
51 import java.net.URL;
52 import java.security.MessageDigest;
53 import java.security.NoSuchAlgorithmException;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.LinkedHashMap;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Map.Entry;
61 import java.util.Random;
62
63 import org.eclipse.jgit.transport.http.HttpConnection;
64 import org.eclipse.jgit.util.Base64;
65 import org.eclipse.jgit.util.GSSManagerFactory;
66 import org.ietf.jgss.GSSContext;
67 import org.ietf.jgss.GSSException;
68 import org.ietf.jgss.GSSManager;
69 import org.ietf.jgss.GSSName;
70 import org.ietf.jgss.Oid;
71
72
73
74
75
76
77
78 abstract class HttpAuthMethod {
79
80
81
82
83 public enum Type {
84 NONE {
85 @Override
86 public HttpAuthMethod method(String hdr) {
87 return None.INSTANCE;
88 }
89
90 @Override
91 public String getSchemeName() {
92 return "None";
93 }
94 },
95 BASIC {
96 @Override
97 public HttpAuthMethod method(String hdr) {
98 return new Basic();
99 }
100
101 @Override
102 public String getSchemeName() {
103 return "Basic";
104 }
105 },
106 DIGEST {
107 @Override
108 public HttpAuthMethod method(String hdr) {
109 return new Digest(hdr);
110 }
111
112 @Override
113 public String getSchemeName() {
114 return "Digest";
115 }
116 },
117 NEGOTIATE {
118 @Override
119 public HttpAuthMethod method(String hdr) {
120 return new Negotiate(hdr);
121 }
122
123 @Override
124 public String getSchemeName() {
125 return "Negotiate";
126 }
127 };
128
129
130
131
132
133
134
135 public abstract HttpAuthMethod method(String hdr);
136
137
138
139
140
141
142 public abstract String getSchemeName();
143 }
144
145 static final String EMPTY_STRING = "";
146 static final String SCHEMA_NAME_SEPARATOR = " ";
147
148
149
150
151
152
153
154
155
156
157 static HttpAuthMethod scanResponse(final HttpConnection conn,
158 Collection<Type> ignoreTypes) {
159 final Map<String, List<String>> headers = conn.getHeaderFields();
160 HttpAuthMethod authentication = Type.NONE.method(EMPTY_STRING);
161
162 for (final Entry<String, List<String>> entry : headers.entrySet()) {
163 if (HDR_WWW_AUTHENTICATE.equalsIgnoreCase(entry.getKey())) {
164 if (entry.getValue() != null) {
165 for (final String value : entry.getValue()) {
166 if (value != null && value.length() != 0) {
167 final String[] valuePart = value.split(
168 SCHEMA_NAME_SEPARATOR, 2);
169
170 try {
171 Type methodType = Type.valueOf(valuePart[0].toUpperCase());
172
173 if ((ignoreTypes != null)
174 && (ignoreTypes.contains(methodType))) {
175 continue;
176 }
177
178 if (authentication.getType().compareTo(methodType) >= 0) {
179 continue;
180 }
181
182 final String param;
183 if (valuePart.length == 1)
184 param = EMPTY_STRING;
185 else
186 param = valuePart[1];
187
188 authentication = methodType
189 .method(param);
190 } catch (IllegalArgumentException e) {
191
192 }
193 }
194 }
195 }
196 break;
197 }
198 }
199
200 return authentication;
201 }
202
203 protected final Type type;
204
205 protected HttpAuthMethod(Type type) {
206 this.type = type;
207 }
208
209
210
211
212
213
214
215
216
217
218
219
220
221 boolean authorize(URIish uri, CredentialsProvider credentialsProvider) {
222 String username;
223 String password;
224
225 if (credentialsProvider != null) {
226 CredentialItem.Username u = new CredentialItem.Username();
227 CredentialItem.Password p = new CredentialItem.Password();
228
229 if (credentialsProvider.supports(u, p)
230 && credentialsProvider.get(uri, u, p)) {
231 username = u.getValue();
232 char[] v = p.getValue();
233 password = (v == null) ? null : new String(p.getValue());
234 p.clear();
235 } else
236 return false;
237 } else {
238 username = uri.getUser();
239 password = uri.getPass();
240 }
241 if (username != null) {
242 authorize(username, password);
243 return true;
244 }
245 return false;
246 }
247
248
249
250
251
252
253
254 abstract void authorize(String user, String pass);
255
256
257
258
259
260
261
262 abstract void configureRequest(HttpConnection conn) throws IOException;
263
264
265
266
267
268
269 public Type getType() {
270 return type;
271 }
272
273
274 private static class None extends HttpAuthMethod {
275 static final None INSTANCE = new None();
276 public None() {
277 super(Type.NONE);
278 }
279
280 @Override
281 void authorize(String user, String pass) {
282
283 }
284
285 @Override
286 void configureRequest(HttpConnection conn) throws IOException {
287
288 }
289 }
290
291
292 private static class Basic extends HttpAuthMethod {
293 private String user;
294
295 private String pass;
296
297 public Basic() {
298 super(Type.BASIC);
299 }
300
301 @Override
302 void authorize(final String username, final String password) {
303 this.user = username;
304 this.pass = password;
305 }
306
307 @Override
308 void configureRequest(final HttpConnection conn) throws IOException {
309 String ident = user + ":" + pass;
310 String enc = Base64.encodeBytes(ident.getBytes("UTF-8"));
311 conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName()
312 + " " + enc);
313 }
314 }
315
316
317 private static class Digest extends HttpAuthMethod {
318 private static final Random PRNG = new Random();
319
320 private final Map<String, String> params;
321
322 private int requestCount;
323
324 private String user;
325
326 private String pass;
327
328 Digest(String hdr) {
329 super(Type.DIGEST);
330 params = parse(hdr);
331
332 final String qop = params.get("qop");
333 if ("auth".equals(qop)) {
334 final byte[] bin = new byte[8];
335 PRNG.nextBytes(bin);
336 params.put("cnonce", Base64.encodeBytes(bin));
337 }
338 }
339
340 @Override
341 void authorize(final String username, final String password) {
342 this.user = username;
343 this.pass = password;
344 }
345
346 @SuppressWarnings("boxing")
347 @Override
348 void configureRequest(final HttpConnection conn) throws IOException {
349 final Map<String, String> r = new LinkedHashMap<String, String>();
350
351 final String realm = params.get("realm");
352 final String nonce = params.get("nonce");
353 final String cnonce = params.get("cnonce");
354 final String uri = uri(conn.getURL());
355 final String qop = params.get("qop");
356 final String method = conn.getRequestMethod();
357
358 final String A1 = user + ":" + realm + ":" + pass;
359 final String A2 = method + ":" + uri;
360
361 r.put("username", user);
362 r.put("realm", realm);
363 r.put("nonce", nonce);
364 r.put("uri", uri);
365
366 final String response, nc;
367 if ("auth".equals(qop)) {
368 nc = String.format("%08x", ++requestCount);
369 response = KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":"
370 + qop + ":"
371 + H(A2));
372 } else {
373 nc = null;
374 response = KD(H(A1), nonce + ":" + H(A2));
375 }
376 r.put("response", response);
377 if (params.containsKey("algorithm"))
378 r.put("algorithm", "MD5");
379 if (cnonce != null && qop != null)
380 r.put("cnonce", cnonce);
381 if (params.containsKey("opaque"))
382 r.put("opaque", params.get("opaque"));
383 if (qop != null)
384 r.put("qop", qop);
385 if (nc != null)
386 r.put("nc", nc);
387
388 StringBuilder v = new StringBuilder();
389 for (Map.Entry<String, String> e : r.entrySet()) {
390 if (v.length() > 0)
391 v.append(", ");
392 v.append(e.getKey());
393 v.append('=');
394 v.append('"');
395 v.append(e.getValue());
396 v.append('"');
397 }
398 conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName()
399 + " " + v);
400 }
401
402 private static String uri(URL u) {
403 StringBuilder r = new StringBuilder();
404 r.append(u.getProtocol());
405 r.append("://"); //$NON-NLS-1$
406 r.append(u.getHost());
407 if (0 < u.getPort()) {
408 if (u.getPort() == 80 && "http".equals(u.getProtocol())) {
409
410 } else if (u.getPort() == 443
411 && "https".equals(u.getProtocol())) {
412
413 } else {
414 r.append(':').append(u.getPort());
415 }
416 }
417 r.append(u.getPath());
418 if (u.getQuery() != null)
419 r.append('?').append(u.getQuery());
420 return r.toString();
421 }
422
423 private static String H(String data) {
424 try {
425 MessageDigest md = newMD5();
426 md.update(data.getBytes("UTF-8"));
427 return LHEX(md.digest());
428 } catch (UnsupportedEncodingException e) {
429 throw new RuntimeException("UTF-8 encoding not available", e);
430 }
431 }
432
433 private static String KD(String secret, String data) {
434 try {
435 MessageDigest md = newMD5();
436 md.update(secret.getBytes("UTF-8"));
437 md.update((byte) ':');
438 md.update(data.getBytes("UTF-8"));
439 return LHEX(md.digest());
440 } catch (UnsupportedEncodingException e) {
441 throw new RuntimeException("UTF-8 encoding not available", e);
442 }
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<String, String>();
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();
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 IOException ioe = new IOException();
559 ioe.initCause(e);
560 throw ioe;
561 }
562 }
563 }
564 }