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