Print this page
Split |
Close |
Expand all |
Collapse all |
--- old/src/share/classes/sun/net/www/protocol/http/DigestAuthentication.java
+++ new/src/share/classes/sun/net/www/protocol/http/DigestAuthentication.java
1 1 /*
2 2 * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved.
3 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 4 *
5 5 * This code is free software; you can redistribute it and/or modify it
6 6 * under the terms of the GNU General Public License version 2 only, as
7 7 * published by the Free Software Foundation. Sun designates this
8 8 * particular file as subject to the "Classpath" exception as provided
9 9 * by Sun in the LICENSE file that accompanied this code.
10 10 *
11 11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 14 * version 2 for more details (a copy is included in the LICENSE file that
15 15 * accompanied this code).
16 16 *
17 17 * You should have received a copy of the GNU General Public License version
18 18 * 2 along with this work; if not, write to the Free Software Foundation,
19 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 20 *
21 21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 23 * have any questions.
24 24 */
25 25
26 26 package sun.net.www.protocol.http;
27 27
28 28 import java.io.*;
29 29 import java.net.URL;
30 30 import java.net.ProtocolException;
31 31 import java.net.PasswordAuthentication;
32 32 import java.util.Arrays;
33 33 import java.util.StringTokenizer;
34 34 import java.util.Random;
35 35
36 36 import sun.net.www.HeaderParser;
37 37 import java.security.MessageDigest;
38 38 import java.security.NoSuchAlgorithmException;
39 39 import static sun.net.www.protocol.http.HttpURLConnection.HTTP_CONNECT;
40 40
41 41 /**
42 42 * DigestAuthentication: Encapsulate an http server authentication using
43 43 * the "Digest" scheme, as described in RFC2069 and updated in RFC2617
44 44 *
45 45 * @author Bill Foote
46 46 */
47 47
48 48 class DigestAuthentication extends AuthenticationInfo {
49 49
50 50 private static final long serialVersionUID = 100L;
51 51
52 52 private String authMethod;
53 53
54 54 // Authentication parameters defined in RFC2617.
55 55 // One instance of these may be shared among several DigestAuthentication
56 56 // instances as a result of a single authorization (for multiple domains)
57 57
58 58 static class Parameters implements java.io.Serializable {
59 59 private static final long serialVersionUID = -3584543755194526252L;
60 60
61 61 private boolean serverQop; // server proposed qop=auth
62 62 private String opaque;
63 63 private String cnonce;
64 64 private String nonce;
65 65 private String algorithm;
66 66 private int NCcount=0;
67 67
68 68 // The H(A1) string used for MD5-sess
69 69 private String cachedHA1;
70 70
71 71 // Force the HA1 value to be recalculated because the nonce has changed
72 72 private boolean redoCachedHA1 = true;
73 73
74 74 private static final int cnonceRepeat = 5;
75 75
76 76 private static final int cnoncelen = 40; /* number of characters in cnonce */
77 77
78 78 private static Random random;
79 79
80 80 static {
81 81 random = new Random();
82 82 }
83 83
84 84 Parameters () {
85 85 serverQop = false;
86 86 opaque = null;
87 87 algorithm = null;
88 88 cachedHA1 = null;
89 89 nonce = null;
90 90 setNewCnonce();
91 91 }
92 92
93 93 boolean authQop () {
94 94 return serverQop;
95 95 }
96 96 synchronized void incrementNC() {
97 97 NCcount ++;
98 98 }
99 99 synchronized int getNCCount () {
100 100 return NCcount;
101 101 }
102 102
103 103 int cnonce_count = 0;
104 104
105 105 /* each call increments the counter */
106 106 synchronized String getCnonce () {
107 107 if (cnonce_count >= cnonceRepeat) {
108 108 setNewCnonce();
109 109 }
110 110 cnonce_count++;
111 111 return cnonce;
112 112 }
113 113 synchronized void setNewCnonce () {
114 114 byte bb[] = new byte [cnoncelen/2];
115 115 char cc[] = new char [cnoncelen];
116 116 random.nextBytes (bb);
117 117 for (int i=0; i<(cnoncelen/2); i++) {
118 118 int x = bb[i] + 128;
119 119 cc[i*2]= (char) ('A'+ x/16);
120 120 cc[i*2+1]= (char) ('A'+ x%16);
121 121 }
122 122 cnonce = new String (cc, 0, cnoncelen);
123 123 cnonce_count = 0;
124 124 redoCachedHA1 = true;
125 125 }
126 126
127 127 synchronized void setQop (String qop) {
128 128 if (qop != null) {
129 129 StringTokenizer st = new StringTokenizer (qop, " ");
130 130 while (st.hasMoreTokens()) {
131 131 if (st.nextToken().equalsIgnoreCase ("auth")) {
132 132 serverQop = true;
133 133 return;
134 134 }
135 135 }
136 136 }
137 137 serverQop = false;
138 138 }
139 139
140 140 synchronized String getOpaque () { return opaque;}
141 141 synchronized void setOpaque (String s) { opaque=s;}
142 142
143 143 synchronized String getNonce () { return nonce;}
144 144
145 145 synchronized void setNonce (String s) {
146 146 if (!s.equals(nonce)) {
147 147 nonce=s;
148 148 NCcount = 0;
149 149 redoCachedHA1 = true;
150 150 }
151 151 }
152 152
153 153 synchronized String getCachedHA1 () {
154 154 if (redoCachedHA1) {
155 155 return null;
156 156 } else {
157 157 return cachedHA1;
158 158 }
159 159 }
160 160
161 161 synchronized void setCachedHA1 (String s) {
162 162 cachedHA1=s;
163 163 redoCachedHA1=false;
164 164 }
165 165
166 166 synchronized String getAlgorithm () { return algorithm;}
167 167 synchronized void setAlgorithm (String s) { algorithm=s;}
168 168 }
169 169
170 170 Parameters params;
171 171
172 172 /**
173 173 * Create a DigestAuthentication
174 174 */
175 175 public DigestAuthentication(boolean isProxy, URL url, String realm,
176 176 String authMethod, PasswordAuthentication pw,
177 177 Parameters params) {
178 178 super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
179 179 AuthScheme.DIGEST,
180 180 url,
181 181 realm);
182 182 this.authMethod = authMethod;
183 183 this.pw = pw;
184 184 this.params = params;
185 185 }
186 186
187 187 public DigestAuthentication(boolean isProxy, String host, int port, String realm,
188 188 String authMethod, PasswordAuthentication pw,
189 189 Parameters params) {
190 190 super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
191 191 AuthScheme.DIGEST,
192 192 host,
↓ open down ↓ |
192 lines elided |
↑ open up ↑ |
193 193 port,
194 194 realm);
195 195 this.authMethod = authMethod;
196 196 this.pw = pw;
197 197 this.params = params;
198 198 }
199 199
200 200 /**
201 201 * @return true if this authentication supports preemptive authorization
202 202 */
203 - boolean supportsPreemptiveAuthorization() {
203 + @Override
204 + public boolean supportsPreemptiveAuthorization() {
204 205 return true;
205 206 }
206 207
207 208 /**
208 - * @return the name of the HTTP header this authentication wants set
209 - */
210 - String getHeaderName() {
211 - if (type == SERVER_AUTHENTICATION) {
212 - return "Authorization";
213 - } else {
214 - return "Proxy-Authorization";
215 - }
216 - }
217 -
218 - /**
219 209 * Reclaculates the request-digest and returns it.
220 210 *
221 211 * <P> Used in the common case where the requestURI is simply the
222 212 * abs_path.
223 213 *
224 214 * @param url
225 215 * the URL
226 216 *
227 217 * @param method
228 218 * the HTTP method
229 219 *
230 220 * @return the value of the HTTP header this authentication wants set
231 221 */
232 - String getHeaderValue(URL url, String method) {
222 + @Override
223 + public String getHeaderValue(URL url, String method) {
233 224 return getHeaderValueImpl(url.getFile(), method);
234 225 }
235 226
236 227 /**
237 228 * Reclaculates the request-digest and returns it.
238 229 *
239 230 * <P> Used when the requestURI is not the abs_path. The exact
240 231 * requestURI can be passed as a String.
241 232 *
242 233 * @param requestURI
243 234 * the Request-URI from the HTTP request line
244 235 *
245 236 * @param method
246 237 * the HTTP method
247 238 *
248 239 * @return the value of the HTTP header this authentication wants set
249 240 */
250 241 String getHeaderValue(String requestURI, String method) {
251 242 return getHeaderValueImpl(requestURI, method);
↓ open down ↓ |
9 lines elided |
↑ open up ↑ |
252 243 }
253 244
254 245 /**
255 246 * Check if the header indicates that the current auth. parameters are stale.
256 247 * If so, then replace the relevant field with the new value
257 248 * and return true. Otherwise return false.
258 249 * returning true means the request can be retried with the same userid/password
259 250 * returning false means we have to go back to the user to ask for a new
260 251 * username password.
261 252 */
262 - boolean isAuthorizationStale (String header) {
253 + @Override
254 + public boolean isAuthorizationStale (String header) {
263 255 HeaderParser p = new HeaderParser (header);
264 256 String s = p.findValue ("stale");
265 257 if (s == null || !s.equals("true"))
266 258 return false;
267 259 String newNonce = p.findValue ("nonce");
268 260 if (newNonce == null || "".equals(newNonce)) {
269 261 return false;
270 262 }
271 263 params.setNonce (newNonce);
272 264 return true;
273 265 }
274 266
275 267 /**
276 268 * Set header(s) on the given connection.
277 269 * @param conn The connection to apply the header(s) to
278 270 * @param p A source of header values for this connection, if needed.
279 271 * @param raw Raw header values for this connection, if needed.
280 272 * @return true if all goes well, false if no headers were set.
281 273 */
282 - boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
274 + @Override
275 + public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
283 276 params.setNonce (p.findValue("nonce"));
284 277 params.setOpaque (p.findValue("opaque"));
285 278 params.setQop (p.findValue("qop"));
286 279
287 280 String uri;
288 281 String method;
289 282 if (type == PROXY_AUTHENTICATION &&
290 283 conn.tunnelState() == HttpURLConnection.TunnelState.SETUP) {
291 284 uri = HttpURLConnection.connectRequestURI(conn.getURL());
292 285 method = HTTP_CONNECT;
293 286 } else {
294 287 uri = conn.getRequestURI();
295 288 method = conn.getMethod();
296 289 }
297 290
298 291 if (params.nonce == null || authMethod == null || pw == null || realm == null) {
299 292 return false;
300 293 }
301 294 if (authMethod.length() >= 1) {
302 295 // Method seems to get converted to all lower case elsewhere.
303 296 // It really does need to start with an upper case letter
304 297 // here.
305 298 authMethod = Character.toUpperCase(authMethod.charAt(0))
306 299 + authMethod.substring(1).toLowerCase();
307 300 }
308 301 String algorithm = p.findValue("algorithm");
309 302 if (algorithm == null || "".equals(algorithm)) {
310 303 algorithm = "MD5"; // The default, accoriding to rfc2069
311 304 }
312 305 params.setAlgorithm (algorithm);
313 306
314 307 // If authQop is true, then the server is doing RFC2617 and
315 308 // has offered qop=auth. We do not support any other modes
316 309 // and if auth is not offered we fallback to the RFC2069 behavior
317 310
318 311 if (params.authQop()) {
319 312 params.setNewCnonce();
320 313 }
321 314
322 315 String value = getHeaderValueImpl (uri, method);
323 316 if (value != null) {
324 317 conn.setAuthenticationProperty(getHeaderName(), value);
325 318 return true;
326 319 } else {
327 320 return false;
328 321 }
329 322 }
330 323
331 324 /* Calculate the Authorization header field given the request URI
332 325 * and based on the authorization information in params
333 326 */
334 327 private String getHeaderValueImpl (String uri, String method) {
335 328 String response;
336 329 char[] passwd = pw.getPassword();
337 330 boolean qop = params.authQop();
338 331 String opaque = params.getOpaque();
339 332 String cnonce = params.getCnonce ();
340 333 String nonce = params.getNonce ();
341 334 String algorithm = params.getAlgorithm ();
342 335 params.incrementNC ();
343 336 int nccount = params.getNCCount ();
344 337 String ncstring=null;
345 338
346 339 if (nccount != -1) {
347 340 ncstring = Integer.toHexString (nccount).toLowerCase();
348 341 int len = ncstring.length();
349 342 if (len < 8)
350 343 ncstring = zeroPad [len] + ncstring;
351 344 }
352 345
353 346 try {
354 347 response = computeDigest(true, pw.getUserName(),passwd,realm,
355 348 method, uri, nonce, cnonce, ncstring);
356 349 } catch (NoSuchAlgorithmException ex) {
357 350 return null;
358 351 }
359 352
360 353 String ncfield = "\"";
361 354 if (qop) {
362 355 ncfield = "\", nc=" + ncstring;
363 356 }
364 357
365 358 String value = authMethod
366 359 + " username=\"" + pw.getUserName()
367 360 + "\", realm=\"" + realm
368 361 + "\", nonce=\"" + nonce
369 362 + ncfield
370 363 + ", uri=\"" + uri
371 364 + "\", response=\"" + response
372 365 + "\", algorithm=\"" + algorithm;
373 366 if (opaque != null) {
374 367 value = value + "\", opaque=\"" + opaque;
375 368 }
376 369 if (cnonce != null) {
377 370 value = value + "\", cnonce=\"" + cnonce;
378 371 }
379 372 if (qop) {
380 373 value = value + "\", qop=\"auth";
381 374 }
382 375 value = value + "\"";
383 376 return value;
384 377 }
385 378
386 379 public void checkResponse (String header, String method, URL url)
387 380 throws IOException {
388 381 checkResponse (header, method, url.getFile());
389 382 }
390 383
391 384 public void checkResponse (String header, String method, String uri)
392 385 throws IOException {
393 386 char[] passwd = pw.getPassword();
394 387 String username = pw.getUserName();
395 388 boolean qop = params.authQop();
396 389 String opaque = params.getOpaque();
397 390 String cnonce = params.cnonce;
398 391 String nonce = params.getNonce ();
399 392 String algorithm = params.getAlgorithm ();
400 393 int nccount = params.getNCCount ();
401 394 String ncstring=null;
402 395
403 396 if (header == null) {
404 397 throw new ProtocolException ("No authentication information in response");
405 398 }
406 399
407 400 if (nccount != -1) {
408 401 ncstring = Integer.toHexString (nccount).toUpperCase();
409 402 int len = ncstring.length();
410 403 if (len < 8)
411 404 ncstring = zeroPad [len] + ncstring;
412 405 }
413 406 try {
414 407 String expected = computeDigest(false, username,passwd,realm,
415 408 method, uri, nonce, cnonce, ncstring);
416 409 HeaderParser p = new HeaderParser (header);
417 410 String rspauth = p.findValue ("rspauth");
418 411 if (rspauth == null) {
419 412 throw new ProtocolException ("No digest in response");
420 413 }
421 414 if (!rspauth.equals (expected)) {
422 415 throw new ProtocolException ("Response digest invalid");
423 416 }
424 417 /* Check if there is a nextnonce field */
425 418 String nextnonce = p.findValue ("nextnonce");
426 419 if (nextnonce != null && ! "".equals(nextnonce)) {
427 420 params.setNonce (nextnonce);
428 421 }
429 422
430 423 } catch (NoSuchAlgorithmException ex) {
431 424 throw new ProtocolException ("Unsupported algorithm in response");
432 425 }
433 426 }
434 427
435 428 private String computeDigest(
436 429 boolean isRequest, String userName, char[] password,
437 430 String realm, String connMethod,
438 431 String requestURI, String nonceString,
439 432 String cnonce, String ncValue
440 433 ) throws NoSuchAlgorithmException
441 434 {
442 435
443 436 String A1, HashA1;
444 437 String algorithm = params.getAlgorithm ();
445 438 boolean md5sess = algorithm.equalsIgnoreCase ("MD5-sess");
446 439
447 440 MessageDigest md = MessageDigest.getInstance(md5sess?"MD5":algorithm);
448 441
449 442 if (md5sess) {
450 443 if ((HashA1 = params.getCachedHA1 ()) == null) {
451 444 String s = userName + ":" + realm + ":";
452 445 String s1 = encode (s, password, md);
453 446 A1 = s1 + ":" + nonceString + ":" + cnonce;
454 447 HashA1 = encode(A1, null, md);
455 448 params.setCachedHA1 (HashA1);
456 449 }
457 450 } else {
458 451 A1 = userName + ":" + realm + ":";
459 452 HashA1 = encode(A1, password, md);
460 453 }
461 454
462 455 String A2;
463 456 if (isRequest) {
464 457 A2 = connMethod + ":" + requestURI;
465 458 } else {
466 459 A2 = ":" + requestURI;
467 460 }
468 461 String HashA2 = encode(A2, null, md);
469 462 String combo, finalHash;
470 463
471 464 if (params.authQop()) { /* RRC2617 when qop=auth */
472 465 combo = HashA1+ ":" + nonceString + ":" + ncValue + ":" +
473 466 cnonce + ":auth:" +HashA2;
474 467
475 468 } else { /* for compatibility with RFC2069 */
476 469 combo = HashA1 + ":" +
477 470 nonceString + ":" +
478 471 HashA2;
479 472 }
480 473 finalHash = encode(combo, null, md);
481 474 return finalHash;
482 475 }
483 476
484 477 private final static char charArray[] = {
485 478 '0', '1', '2', '3', '4', '5', '6', '7',
486 479 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
487 480 };
488 481
489 482 private final static String zeroPad[] = {
490 483 // 0 1 2 3 4 5 6 7
491 484 "00000000", "0000000", "000000", "00000", "0000", "000", "00", "0"
492 485 };
493 486
494 487 private String encode(String src, char[] passwd, MessageDigest md) {
495 488 try {
496 489 md.update(src.getBytes("ISO-8859-1"));
497 490 } catch (java.io.UnsupportedEncodingException uee) {
498 491 assert false;
499 492 }
500 493 if (passwd != null) {
501 494 byte[] passwdBytes = new byte[passwd.length];
502 495 for (int i=0; i<passwd.length; i++)
503 496 passwdBytes[i] = (byte)passwd[i];
504 497 md.update(passwdBytes);
505 498 Arrays.fill(passwdBytes, (byte)0x00);
506 499 }
507 500 byte[] digest = md.digest();
508 501
509 502 StringBuffer res = new StringBuffer(digest.length * 2);
510 503 for (int i = 0; i < digest.length; i++) {
511 504 int hashchar = ((digest[i] >>> 4) & 0xf);
512 505 res.append(charArray[hashchar]);
513 506 hashchar = (digest[i] & 0xf);
514 507 res.append(charArray[hashchar]);
515 508 }
516 509 return res.toString();
517 510 }
518 511 }
↓ open down ↓ |
226 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX