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.Locale;
60 import java.util.Map;
61 import java.util.Map.Entry;
62 import java.util.Random;
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 (final Entry<String, List<String>> entry : headers.entrySet()) {
164 if (HDR_WWW_AUTHENTICATE.equalsIgnoreCase(entry.getKey())) {
165 if (entry.getValue() != null) {
166 for (final 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 protected HttpAuthMethod(Type type) {
208 this.type = type;
209 }
210
211
212
213
214
215
216
217
218
219
220
221
222
223 boolean authorize(URIish uri, CredentialsProvider credentialsProvider) {
224 String username;
225 String password;
226
227 if (credentialsProvider != null) {
228 CredentialItem.Username u = new CredentialItem.Username();
229 CredentialItem.Password p = new CredentialItem.Password();
230
231 if (credentialsProvider.supports(u, p)
232 && credentialsProvider.get(uri, u, p)) {
233 username = u.getValue();
234 char[] v = p.getValue();
235 password = (v == null) ? null : new String(p.getValue());
236 p.clear();
237 } else
238 return false;
239 } else {
240 username = uri.getUser();
241 password = uri.getPass();
242 }
243 if (username != null) {
244 authorize(username, password);
245 return true;
246 }
247 return false;
248 }
249
250
251
252
253
254
255
256 abstract void authorize(String user, String pass);
257
258
259
260
261
262
263
264 abstract void configureRequest(HttpConnection conn) throws IOException;
265
266
267
268
269
270
271 public Type getType() {
272 return type;
273 }
274
275
276 private static class None extends HttpAuthMethod {
277 static final None INSTANCE = new None();
278 public None() {
279 super(Type.NONE);
280 }
281
282 @Override
283 void authorize(String user, String pass) {
284
285 }
286
287 @Override
288 void configureRequest(HttpConnection conn) throws IOException {
289
290 }
291 }
292
293
294 private static class Basic extends HttpAuthMethod {
295 private String user;
296
297 private String pass;
298
299 public Basic() {
300 super(Type.BASIC);
301 }
302
303 @Override
304 void authorize(final String username, final String password) {
305 this.user = username;
306 this.pass = password;
307 }
308
309 @Override
310 void configureRequest(final HttpConnection conn) throws IOException {
311 String ident = user + ":" + pass;
312 String enc = Base64.encodeBytes(ident.getBytes("UTF-8"));
313 conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName()
314 + " " + enc);
315 }
316 }
317
318
319 private static class Digest extends HttpAuthMethod {
320 private static final Random PRNG = new Random();
321
322 private final Map<String, String> params;
323
324 private int requestCount;
325
326 private String user;
327
328 private String pass;
329
330 Digest(String hdr) {
331 super(Type.DIGEST);
332 params = parse(hdr);
333
334 final String qop = params.get("qop");
335 if ("auth".equals(qop)) {
336 final byte[] bin = new byte[8];
337 PRNG.nextBytes(bin);
338 params.put("cnonce", Base64.encodeBytes(bin));
339 }
340 }
341
342 @Override
343 void authorize(final String username, final String password) {
344 this.user = username;
345 this.pass = password;
346 }
347
348 @SuppressWarnings("boxing")
349 @Override
350 void configureRequest(final HttpConnection conn) throws IOException {
351 final Map<String, String> r = new LinkedHashMap<>();
352
353 final String realm = params.get("realm");
354 final String nonce = params.get("nonce");
355 final String cnonce = params.get("cnonce");
356 final String uri = uri(conn.getURL());
357 final String qop = params.get("qop");
358 final String method = conn.getRequestMethod();
359
360 final String A1 = user + ":" + realm + ":" + pass;
361 final String A2 = method + ":" + uri;
362
363 r.put("username", user);
364 r.put("realm", realm);
365 r.put("nonce", nonce);
366 r.put("uri", uri);
367
368 final String response, nc;
369 if ("auth".equals(qop)) {
370 nc = String.format("%08x", ++requestCount);
371 response = KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":"
372 + qop + ":"
373 + H(A2));
374 } else {
375 nc = null;
376 response = KD(H(A1), nonce + ":" + H(A2));
377 }
378 r.put("response", response);
379 if (params.containsKey("algorithm"))
380 r.put("algorithm", "MD5");
381 if (cnonce != null && qop != null)
382 r.put("cnonce", cnonce);
383 if (params.containsKey("opaque"))
384 r.put("opaque", params.get("opaque"));
385 if (qop != null)
386 r.put("qop", qop);
387 if (nc != null)
388 r.put("nc", nc);
389
390 StringBuilder v = new StringBuilder();
391 for (Map.Entry<String, String> e : r.entrySet()) {
392 if (v.length() > 0)
393 v.append(", ");
394 v.append(e.getKey());
395 v.append('=');
396 v.append('"');
397 v.append(e.getValue());
398 v.append('"');
399 }
400 conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName()
401 + " " + v);
402 }
403
404 private static String uri(URL u) {
405 StringBuilder r = new StringBuilder();
406 r.append(u.getProtocol());
407 r.append("://"); //$NON-NLS-1$
408 r.append(u.getHost());
409 if (0 < u.getPort()) {
410 if (u.getPort() == 80 && "http".equals(u.getProtocol())) {
411
412 } else if (u.getPort() == 443
413 && "https".equals(u.getProtocol())) {
414
415 } else {
416 r.append(':').append(u.getPort());
417 }
418 }
419 r.append(u.getPath());
420 if (u.getQuery() != null)
421 r.append('?').append(u.getQuery());
422 return r.toString();
423 }
424
425 private static String H(String data) {
426 try {
427 MessageDigest md = newMD5();
428 md.update(data.getBytes("UTF-8"));
429 return LHEX(md.digest());
430 } catch (UnsupportedEncodingException e) {
431 throw new RuntimeException("UTF-8 encoding not available", e);
432 }
433 }
434
435 private static String KD(String secret, String data) {
436 try {
437 MessageDigest md = newMD5();
438 md.update(secret.getBytes("UTF-8"));
439 md.update((byte) ':');
440 md.update(data.getBytes("UTF-8"));
441 return LHEX(md.digest());
442 } catch (UnsupportedEncodingException e) {
443 throw new RuntimeException("UTF-8 encoding not available", e);
444 }
445 }
446
447 private static MessageDigest newMD5() {
448 try {
449 return MessageDigest.getInstance("MD5");
450 } catch (NoSuchAlgorithmException e) {
451 throw new RuntimeException("No MD5 available", e);
452 }
453 }
454
455 private static final char[] LHEX = { '0', '1', '2', '3', '4', '5', '6',
456 '7', '8', '9',
457 'a', 'b', 'c', 'd', 'e', 'f' };
458
459 private static String LHEX(byte[] bin) {
460 StringBuilder r = new StringBuilder(bin.length * 2);
461 for (int i = 0; i < bin.length; i++) {
462 byte b = bin[i];
463 r.append(LHEX[(b >>> 4) & 0x0f]);
464 r.append(LHEX[b & 0x0f]);
465 }
466 return r.toString();
467 }
468
469 private static Map<String, String> parse(String auth) {
470 Map<String, String> p = new HashMap<>();
471 int next = 0;
472 while (next < auth.length()) {
473 if (next < auth.length() && auth.charAt(next) == ',') {
474 next++;
475 }
476 while (next < auth.length()
477 && Character.isWhitespace(auth.charAt(next))) {
478 next++;
479 }
480
481 int eq = auth.indexOf('=', next);
482 if (eq < 0 || eq + 1 == auth.length()) {
483 return Collections.emptyMap();
484 }
485
486 final String name = auth.substring(next, eq);
487 final String value;
488 if (auth.charAt(eq + 1) == '"') {
489 int dq = auth.indexOf('"', eq + 2);
490 if (dq < 0) {
491 return Collections.emptyMap();
492 }
493 value = auth.substring(eq + 2, dq);
494 next = dq + 1;
495
496 } else {
497 int space = auth.indexOf(' ', eq + 1);
498 int comma = auth.indexOf(',', eq + 1);
499 if (space < 0)
500 space = auth.length();
501 if (comma < 0)
502 comma = auth.length();
503
504 final int e = Math.min(space, comma);
505 value = auth.substring(eq + 1, e);
506 next = e + 1;
507 }
508 p.put(name, value);
509 }
510 return p;
511 }
512 }
513
514 private static class Negotiate extends HttpAuthMethod {
515 private static final GSSManagerFactory GSS_MANAGER_FACTORY = GSSManagerFactory
516 .detect();
517
518 private static final Oid OID;
519 static {
520 try {
521
522 OID = new Oid("1.3.6.1.5.5.2");
523 } catch (GSSException e) {
524 throw new Error("Cannot create NEGOTIATE oid.", e);
525 }
526 }
527
528 private final byte[] prevToken;
529
530 public Negotiate(String hdr) {
531 super(Type.NEGOTIATE);
532 prevToken = Base64.decode(hdr);
533 }
534
535 @Override
536 void authorize(String user, String pass) {
537
538 }
539
540 @Override
541 void configureRequest(HttpConnection conn) throws IOException {
542 GSSManager gssManager = GSS_MANAGER_FACTORY.newInstance(conn
543 .getURL());
544 String host = conn.getURL().getHost();
545 String peerName = "HTTP@" + host.toLowerCase(Locale.ROOT);
546 try {
547 GSSName gssName = gssManager.createName(peerName,
548 GSSName.NT_HOSTBASED_SERVICE);
549 GSSContext context = gssManager.createContext(gssName, OID,
550 null, GSSContext.DEFAULT_LIFETIME);
551
552 context.requestCredDeleg(true);
553
554 byte[] token = context.initSecContext(prevToken, 0,
555 prevToken.length);
556
557 conn.setRequestProperty(HDR_AUTHORIZATION, getType().getSchemeName()
558 + " " + Base64.encodeBytes(token));
559 } catch (GSSException e) {
560 IOException ioe = new IOException();
561 ioe.initCause(e);
562 throw ioe;
563 }
564 }
565 }
566 }