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