View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v 1.4.2.4 2003/10/04 02:31:25 mbecke Exp $ 3 * $Revision: 1.4.2.4 $ 4 * $Date: 2003/10/04 02:31:25 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 2002-2003 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>. 59 * 60 * [Additional notices, if required by prior licensing conditions] 61 * 62 */ 63 64 package org.apache.commons.httpclient.auth; 65 66 import java.util.Map; 67 import java.security.MessageDigest; 68 69 import org.apache.commons.httpclient.HttpConstants; 70 import org.apache.commons.httpclient.Credentials; 71 import org.apache.commons.httpclient.UsernamePasswordCredentials; 72 import org.apache.commons.logging.Log; 73 import org.apache.commons.logging.LogFactory; 74 75 /*** 76 * <p> 77 * Digest authentication scheme as defined in RFC 2617. 78 * </p> 79 * 80 * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> 81 * @author Rodney Waldhoff 82 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> 83 * @author Ortwin Gl�ck 84 * @author Sean C. Sullivan 85 * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> 86 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> 87 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> 88 */ 89 90 public class DigestScheme extends RFC2617Scheme { 91 92 /*** Log object for this class. */ 93 private static final Log LOG = LogFactory.getLog(DigestScheme.class); 94 95 /*** 96 * Hexa values used when creating 32 character long digest in HTTP DigestScheme 97 * in case of authentication. 98 * 99 * @see #encode(byte[]) 100 */ 101 private static final char[] HEXADECIMAL = { 102 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 103 'e', 'f' 104 }; 105 106 /*** 107 * Gets an ID based upon the realm and the nonce value. This ensures that requests 108 * to the same realm with different nonce values will succeed. This differentiation 109 * allows servers to request re-authentication using a fresh nonce value. 110 * 111 * @return the realm plus the nonce value, if present 112 */ 113 public String getID() { 114 115 String id = getRealm(); 116 String nonce = getParameter("nonce"); 117 if (nonce != null) { 118 id += "-" + nonce; 119 } 120 121 return id; 122 } 123 124 /*** 125 * Constructor for the digest authentication scheme. 126 * 127 * @param challenge The authentication challenge 128 * 129 * @throws MalformedChallengeException is thrown if the authentication challenge 130 * is malformed 131 */ 132 public DigestScheme(final String challenge) 133 throws MalformedChallengeException { 134 super(challenge); 135 this.getParameters().put("nc", "00000001"); 136 } 137 138 139 /*** 140 * Returns textual designation of the digest authentication scheme. 141 * 142 * @return <code>digest</code> 143 */ 144 public String getSchemeName() { 145 return "digest"; 146 } 147 148 /*** 149 * Produces a digest authorization string for the given set of 150 * {@link Credentials}, method name and URI. 151 * 152 * @param credentials A set of credentials to be used for athentication 153 * @param method the name of the method that requires authorization. 154 * @param uri The URI for which authorization is needed. 155 * 156 * @throws AuthenticationException if authorization string cannot 157 * be generated due to an authentication failure 158 * 159 * @return a digest authorization string 160 * 161 * @see org.apache.commons.httpclient.HttpMethod#getName() 162 * @see org.apache.commons.httpclient.HttpMethod#getPath() 163 */ 164 public String authenticate(Credentials credentials, String method, String uri) 165 throws AuthenticationException { 166 167 LOG.trace("enter DigestScheme.authenticate(Credentials, String, String)"); 168 169 UsernamePasswordCredentials usernamepassword = null; 170 try { 171 usernamepassword = (UsernamePasswordCredentials) credentials; 172 } catch (ClassCastException e) { 173 throw new AuthenticationException( 174 "Credentials cannot be used for digest authentication: " 175 + credentials.getClass().getName()); 176 } 177 this.getParameters().put("cnonce", createCnonce()); 178 this.getParameters().put("methodname", method); 179 this.getParameters().put("uri", uri); 180 return DigestScheme.authenticate(usernamepassword, getParameters()); 181 } 182 183 /*** 184 * Produces a digest authorization string for the given set of 185 * {@link UsernamePasswordCredentials} and authetication parameters. 186 * 187 * @param credentials Credentials to create the digest with 188 * @param params The authetication parameters. The following 189 * parameters are expected: <code>uri</code>, <code>realm</code>, 190 * <code>nonce</code>, <code>nc</code>, <code>cnonce</code>, 191 * <code>qop</code>, <code>methodname</code>. 192 * 193 * @return a digest authorization string 194 * 195 * @throws AuthenticationException if authorization string cannot 196 * be generated due to an authentication failure 197 */ 198 public static String authenticate(UsernamePasswordCredentials credentials, 199 Map params) throws AuthenticationException { 200 201 LOG.trace("enter DigestScheme.authenticate(UsernamePasswordCredentials, Map)"); 202 203 String digest = createDigest(credentials.getUserName(), 204 credentials.getPassword(), params); 205 206 return "Digest " + createDigestHeader(credentials.getUserName(), 207 params, digest); 208 } 209 210 /*** 211 * Creates an MD5 response digest. 212 * 213 * @param uname Username 214 * @param pwd Password 215 * @param params The parameters necessary to construct the digest. 216 * The following parameters are expected: <code>uri</code>, 217 * <code>realm</code>, <code>nonce</code>, <code>nc</code>, 218 * <code>cnonce</code>, <code>qop</code>, <code>methodname</code>. 219 * 220 * @return The created digest as string. This will be the response tag's 221 * value in the Authentication HTTP header. 222 * @throws AuthenticationException when MD5 is an unsupported algorithm 223 */ 224 public static String createDigest(String uname, String pwd, 225 Map params) throws AuthenticationException { 226 227 LOG.trace("enter DigestScheme.createDigest(String, String, Map)"); 228 229 final String digAlg = "MD5"; 230 231 // Collecting required tokens 232 String uri = (String) params.get("uri"); 233 String realm = (String) params.get("realm"); 234 String nonce = (String) params.get("nonce"); 235 String nc = (String) params.get("nc"); 236 String cnonce = (String) params.get("cnonce"); 237 String qop = (String) params.get("qop"); 238 String method = (String) params.get("methodname"); 239 String algorithm = (String) params.get("algorithm"); 240 241 // If an algorithm is not specified, default to MD5. 242 if(algorithm == null) { 243 algorithm="MD5"; 244 } 245 246 if (qop != null) { 247 qop = "auth"; 248 } 249 250 MessageDigest md5Helper; 251 252 try { 253 md5Helper = MessageDigest.getInstance(digAlg); 254 } catch (Exception e) { 255 throw new AuthenticationException( 256 "Unsupported algorithm in HTTP Digest authentication: " 257 + digAlg); 258 } 259 260 // Calculating digest according to rfc 2617 261 262 String a1 = null; 263 if(algorithm.equals("MD5")) { 264 // unq(username-value) ":" unq(realm-value) ":" passwd 265 a1 = uname + ":" + realm + ":" + pwd; 266 } else if(algorithm.equals("MD5-sess")) { 267 // H( unq(username-value) ":" unq(realm-value) ":" passwd ) 268 // ":" unq(nonce-value) 269 // ":" unq(cnonce-value) 270 271 String tmp=encode(md5Helper.digest(HttpConstants.getBytes( 272 uname + ":" + realm + ":" + pwd))); 273 274 a1 = tmp + ":" + nonce + ":" + cnonce; 275 } else { 276 LOG.warn("Unhandled algorithm " + algorithm + " requested"); 277 a1 = uname + ":" + realm + ":" + pwd; 278 } 279 String md5a1 = encode(md5Helper.digest(HttpConstants.getBytes(a1))); 280 String serverDigestValue; 281 282 String a2 = method + ":" + uri; 283 String md5a2 = encode(md5Helper.digest(HttpConstants.getBytes(a2))); 284 285 if (qop == null) { 286 LOG.debug("Using null qop method"); 287 serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2; 288 } else { 289 LOG.debug("Using qop method " + qop); 290 serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce 291 + ":" + qop + ":" + md5a2; 292 } 293 294 String serverDigest = 295 encode(md5Helper.digest(HttpConstants.getBytes(serverDigestValue))); 296 297 return serverDigest; 298 } 299 300 /*** 301 * Creates digest-response header as defined in RFC2617. 302 * 303 * @param uname Username 304 * @param params The parameters necessary to construct the digest header. 305 * The following parameters are expected: <code>uri</code>, 306 * <code>realm</code>, <code>nonce</code>, <code>nc</code>, 307 * <code>cnonce</code>, <code>qop</code>, <code>methodname</code>. 308 * @param digest The response tag's value as String. 309 * 310 * @return The digest-response as String. 311 */ 312 public static String createDigestHeader(String uname, Map params, 313 String digest) { 314 315 LOG.trace("enter DigestScheme.createDigestHeader(String, Map, " 316 + "String)"); 317 318 StringBuffer sb = new StringBuffer(); 319 String uri = (String) params.get("uri"); 320 String realm = (String) params.get("realm"); 321 String nonce = (String) params.get("nonce"); 322 String nc = (String) params.get("nc"); 323 String cnonce = (String) params.get("cnonce"); 324 String opaque = (String) params.get("opaque"); 325 String response = digest; 326 String qop = (String) params.get("qop"); 327 String algorithm = (String) params.get("algorithm"); 328 329 if (qop != null) { 330 qop = "auth"; //we only support auth 331 } 332 333 sb.append("username=\"" + uname + "\"") 334 .append(", realm=\"" + realm + "\"") 335 .append(", nonce=\"" + nonce + "\"").append(", uri=\"" + uri + "\"") 336 .append(((qop == null) ? "" : ", qop=\"" + qop + "\"")) 337 .append(", algorithm=\"" + algorithm + "\"") 338 .append(((qop == null) ? "" : ", nc=" + nc)) 339 .append(((qop == null) ? "" : ", cnonce=\"" + cnonce + "\"")) 340 .append(", response=\"" + response + "\"") 341 .append((opaque == null) ? "" : ", opaque=\"" + opaque + "\""); 342 343 return sb.toString(); 344 } 345 346 347 /*** 348 * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long 349 * <CODE>String</CODE> according to RFC 2617. 350 * 351 * @param binaryData array containing the digest 352 * @return encoded MD5, or <CODE>null</CODE> if encoding failed 353 */ 354 private static String encode(byte[] binaryData) { 355 LOG.trace("enter DigestScheme.encode(byte[])"); 356 357 if (binaryData.length != 16) { 358 return null; 359 } 360 361 char[] buffer = new char[32]; 362 for (int i = 0; i < 16; i++) { 363 int low = (int) (binaryData[i] & 0x0f); 364 int high = (int) ((binaryData[i] & 0xf0) >> 4); 365 buffer[i * 2] = HEXADECIMAL[high]; 366 buffer[(i * 2) + 1] = HEXADECIMAL[low]; 367 } 368 369 return new String(buffer); 370 } 371 372 373 /*** 374 * Creates a random cnonce value based on the current time. 375 * 376 * @return The cnonce value as String. 377 * @throws AuthenticationException if MD5 algorithm is not supported. 378 */ 379 public static String createCnonce() throws AuthenticationException { 380 LOG.trace("enter DigestScheme.createCnonce()"); 381 382 String cnonce; 383 final String digAlg = "MD5"; 384 MessageDigest md5Helper; 385 386 try { 387 md5Helper = MessageDigest.getInstance(digAlg); 388 } catch (Exception e) { 389 throw new AuthenticationException( 390 "Unsupported algorithm in HTTP Digest authentication: " 391 + digAlg); 392 } 393 394 cnonce = Long.toString(System.currentTimeMillis()); 395 cnonce = encode(md5Helper.digest(HttpConstants.getBytes(cnonce))); 396 397 return cnonce; 398 } 399 }

This page was automatically generated by Maven