KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > httpclient > auth > DigestScheme


1 /*
2  * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v 1.4.2.7 2004/02/22 18:21:14 olegk Exp $
3  * $Revision: 1.4.2.7 $
4  * $Date: 2004/02/22 18:21:14 $
5  *
6  * ====================================================================
7  *
8  * Copyright 2002-2004 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ====================================================================
22  *
23  * This software consists of voluntary contributions made by many
24  * individuals on behalf of the Apache Software Foundation. For more
25  * information on the Apache Software Foundation, please see
26  * <http://www.apache.org/>.
27  *
28  * [Additional notices, if required by prior licensing conditions]
29  *
30  */

31
32 package org.apache.commons.httpclient.auth;
33
34 import java.util.Map JavaDoc;
35 import java.security.MessageDigest JavaDoc;
36
37 import org.apache.commons.httpclient.HttpConstants;
38 import org.apache.commons.httpclient.Credentials;
39 import org.apache.commons.httpclient.UsernamePasswordCredentials;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43 /**
44  * <p>
45  * Digest authentication scheme as defined in RFC 2617.
46  * </p>
47  *
48  * @author <a HREF="mailto:remm@apache.org">Remy Maucherat</a>
49  * @author Rodney Waldhoff
50  * @author <a HREF="mailto:jsdever@apache.org">Jeff Dever</a>
51  * @author Ortwin Glück
52  * @author Sean C. Sullivan
53  * @author <a HREF="mailto:adrian@ephox.com">Adrian Sutton</a>
54  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
55  * @author <a HREF="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
56  */

57
58 public class DigestScheme extends RFC2617Scheme {
59     
60     /** Log object for this class. */
61     private static final Log LOG = LogFactory.getLog(DigestScheme.class);
62
63     /**
64      * Hexa values used when creating 32 character long digest in HTTP DigestScheme
65      * in case of authentication.
66      *
67      * @see #encode(byte[])
68      */

69     private static final char[] HEXADECIMAL = {
70         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
71         'e', 'f'
72     };
73
74     /**
75      * Gets an ID based upon the realm and the nonce value. This ensures that requests
76      * to the same realm with different nonce values will succeed. This differentiation
77      * allows servers to request re-authentication using a fresh nonce value.
78      *
79      * @return the realm plus the nonce value, if present
80      */

81     public String JavaDoc getID() {
82         
83         String JavaDoc id = getRealm();
84         String JavaDoc nonce = getParameter("nonce");
85         if (nonce != null) {
86             id += "-" + nonce;
87         }
88         
89         return id;
90     }
91
92     /**
93      * Constructor for the digest authentication scheme.
94      *
95      * @param challenge The authentication challenge
96      *
97      * @throws MalformedChallengeException is thrown if the authentication challenge
98      * is malformed
99      */

100     public DigestScheme(final String JavaDoc challenge)
101       throws MalformedChallengeException {
102         super(challenge);
103         if (this.getParameter("realm") == null) {
104             throw new MalformedChallengeException("realm missing");
105         }
106         if (this.getParameter("nonce") == null) {
107             throw new MalformedChallengeException("nonce missing");
108         }
109         this.getParameters().put("nc", "00000001");
110     }
111
112
113     /**
114      * Returns textual designation of the digest authentication scheme.
115      *
116      * @return <code>digest</code>
117      */

118     public String JavaDoc getSchemeName() {
119         return "digest";
120     }
121
122     /**
123      * Produces a digest authorization string for the given set of
124      * {@link Credentials}, method name and URI.
125      *
126      * @param credentials A set of credentials to be used for athentication
127      * @param method the name of the method that requires authorization.
128      * @param uri The URI for which authorization is needed.
129      *
130      * @throws AuthenticationException if authorization string cannot
131      * be generated due to an authentication failure
132      *
133      * @return a digest authorization string
134      *
135      * @see org.apache.commons.httpclient.HttpMethod#getName()
136      * @see org.apache.commons.httpclient.HttpMethod#getPath()
137      */

138     public String JavaDoc authenticate(Credentials credentials, String JavaDoc method, String JavaDoc uri)
139       throws AuthenticationException {
140
141         LOG.trace("enter DigestScheme.authenticate(Credentials, String, String)");
142
143         UsernamePasswordCredentials usernamepassword = null;
144         try {
145             usernamepassword = (UsernamePasswordCredentials) credentials;
146         } catch (ClassCastException JavaDoc e) {
147             throw new AuthenticationException(
148              "Credentials cannot be used for digest authentication: "
149               + credentials.getClass().getName());
150         }
151         this.getParameters().put("cnonce", createCnonce());
152         this.getParameters().put("methodname", method);
153         this.getParameters().put("uri", uri);
154         return DigestScheme.authenticate(usernamepassword, getParameters());
155     }
156
157     /**
158      * Produces a digest authorization string for the given set of
159      * {@link UsernamePasswordCredentials} and authetication parameters.
160      *
161      * @param credentials Credentials to create the digest with
162      * @param params The authetication parameters. The following
163      * parameters are expected: <code>uri</code>, <code>realm</code>,
164      * <code>nonce</code>, <code>nc</code>, <code>cnonce</code>,
165      * <code>qop</code>, <code>methodname</code>.
166      *
167      * @return a digest authorization string
168      *
169      * @throws AuthenticationException if authorization string cannot
170      * be generated due to an authentication failure
171      */

172     public static String JavaDoc authenticate(UsernamePasswordCredentials credentials,
173             Map JavaDoc params) throws AuthenticationException {
174
175         LOG.trace("enter DigestScheme.authenticate(UsernamePasswordCredentials, Map)");
176
177         String JavaDoc digest = createDigest(credentials.getUserName(),
178                 credentials.getPassword(), params);
179
180         return "Digest " + createDigestHeader(credentials.getUserName(),
181                 params, digest);
182     }
183
184     /**
185      * Creates an MD5 response digest.
186      *
187      * @param uname Username
188      * @param pwd Password
189      * @param params The parameters necessary to construct the digest.
190      * The following parameters are expected: <code>uri</code>,
191      * <code>realm</code>, <code>nonce</code>, <code>nc</code>,
192      * <code>cnonce</code>, <code>qop</code>, <code>methodname</code>.
193      *
194      * @return The created digest as string. This will be the response tag's
195      * value in the Authentication HTTP header.
196      * @throws AuthenticationException when MD5 is an unsupported algorithm
197      */

198     public static String JavaDoc createDigest(String JavaDoc uname, String JavaDoc pwd,
199             Map JavaDoc params) throws AuthenticationException {
200
201         LOG.trace("enter DigestScheme.createDigest(String, String, Map)");
202
203         final String JavaDoc digAlg = "MD5";
204
205         // Collecting required tokens
206
String JavaDoc uri = (String JavaDoc) params.get("uri");
207         String JavaDoc realm = (String JavaDoc) params.get("realm");
208         String JavaDoc nonce = (String JavaDoc) params.get("nonce");
209         String JavaDoc nc = (String JavaDoc) params.get("nc");
210         String JavaDoc cnonce = (String JavaDoc) params.get("cnonce");
211         String JavaDoc qop = (String JavaDoc) params.get("qop");
212         String JavaDoc method = (String JavaDoc) params.get("methodname");
213         String JavaDoc algorithm = (String JavaDoc) params.get("algorithm");
214
215         // If an algorithm is not specified, default to MD5.
216
if(algorithm == null) {
217             algorithm="MD5";
218         }
219
220         if (qop != null) {
221             qop = "auth";
222         }
223
224         MessageDigest JavaDoc md5Helper;
225
226         try {
227             md5Helper = MessageDigest.getInstance(digAlg);
228         } catch (Exception JavaDoc e) {
229             throw new AuthenticationException(
230               "Unsupported algorithm in HTTP Digest authentication: "
231                + digAlg);
232         }
233
234         // Calculating digest according to rfc 2617
235

236         String JavaDoc a1 = null;
237         if(algorithm.equals("MD5")) {
238             // unq(username-value) ":" unq(realm-value) ":" passwd
239
a1 = uname + ":" + realm + ":" + pwd;
240         } else if(algorithm.equals("MD5-sess")) {
241             // H( unq(username-value) ":" unq(realm-value) ":" passwd )
242
// ":" unq(nonce-value)
243
// ":" unq(cnonce-value)
244

245             String JavaDoc tmp=encode(md5Helper.digest(HttpConstants.getContentBytes(
246                 uname + ":" + realm + ":" + pwd)));
247
248             a1 = tmp + ":" + nonce + ":" + cnonce;
249         } else {
250             LOG.warn("Unhandled algorithm " + algorithm + " requested");
251             a1 = uname + ":" + realm + ":" + pwd;
252         }
253         String JavaDoc md5a1 = encode(md5Helper.digest(HttpConstants.getContentBytes(a1)));
254         String JavaDoc serverDigestValue;
255
256         String JavaDoc a2 = method + ":" + uri;
257         String JavaDoc md5a2 = encode(md5Helper.digest(HttpConstants.getBytes(a2)));
258
259         if (qop == null) {
260             LOG.debug("Using null qop method");
261             serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2;
262         } else {
263             LOG.debug("Using qop method " + qop);
264             serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce
265                                 + ":" + qop + ":" + md5a2;
266         }
267
268         String JavaDoc serverDigest =
269             encode(md5Helper.digest(HttpConstants.getBytes(serverDigestValue)));
270
271         return serverDigest;
272     }
273
274     /**
275      * Creates digest-response header as defined in RFC2617.
276      *
277      * @param uname Username
278      * @param params The parameters necessary to construct the digest header.
279      * The following parameters are expected: <code>uri</code>,
280      * <code>realm</code>, <code>nonce</code>, <code>nc</code>,
281      * <code>cnonce</code>, <code>qop</code>, <code>methodname</code>.
282      * @param digest The response tag's value as String.
283      *
284      * @return The digest-response as String.
285      */

286     public static String JavaDoc createDigestHeader(String JavaDoc uname, Map JavaDoc params,
287             String JavaDoc digest) {
288
289         LOG.trace("enter DigestScheme.createDigestHeader(String, Map, "
290             + "String)");
291
292         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
293         String JavaDoc uri = (String JavaDoc) params.get("uri");
294         String JavaDoc realm = (String JavaDoc) params.get("realm");
295         String JavaDoc nonce = (String JavaDoc) params.get("nonce");
296         String JavaDoc nc = (String JavaDoc) params.get("nc");
297         String JavaDoc cnonce = (String JavaDoc) params.get("cnonce");
298         String JavaDoc opaque = (String JavaDoc) params.get("opaque");
299         String JavaDoc response = digest;
300         String JavaDoc qop = (String JavaDoc) params.get("qop");
301         String JavaDoc algorithm = (String JavaDoc) params.get("algorithm");
302
303         if (qop != null) {
304             qop = "auth"; //we only support auth
305
}
306
307         sb.append("username=\"" + uname + "\"")
308           .append(", realm=\"" + realm + "\"")
309           .append(", nonce=\"" + nonce + "\"")
310           .append(", uri=\"" + uri + "\"")
311           .append(((qop == null) ? "" : ", qop=\"" + qop + "\""))
312           .append((algorithm == null) ? "" : ", algorithm=\"" + algorithm + "\"")
313           .append(((qop == null) ? "" : ", nc=" + nc))
314           .append(((qop == null) ? "" : ", cnonce=\"" + cnonce + "\""))
315           .append(", response=\"" + response + "\"")
316           .append((opaque == null) ? "" : ", opaque=\"" + opaque + "\"");
317
318         return sb.toString();
319     }
320
321
322     /**
323      * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long
324      * <CODE>String</CODE> according to RFC 2617.
325      *
326      * @param binaryData array containing the digest
327      * @return encoded MD5, or <CODE>null</CODE> if encoding failed
328      */

329     private static String JavaDoc encode(byte[] binaryData) {
330         LOG.trace("enter DigestScheme.encode(byte[])");
331
332         if (binaryData.length != 16) {
333             return null;
334         }
335
336         char[] buffer = new char[32];
337         for (int i = 0; i < 16; i++) {
338             int low = (int) (binaryData[i] & 0x0f);
339             int high = (int) ((binaryData[i] & 0xf0) >> 4);
340             buffer[i * 2] = HEXADECIMAL[high];
341             buffer[(i * 2) + 1] = HEXADECIMAL[low];
342         }
343
344         return new String JavaDoc(buffer);
345     }
346
347
348     /**
349      * Creates a random cnonce value based on the current time.
350      *
351      * @return The cnonce value as String.
352      * @throws AuthenticationException if MD5 algorithm is not supported.
353      */

354     public static String JavaDoc createCnonce() throws AuthenticationException {
355         LOG.trace("enter DigestScheme.createCnonce()");
356
357         String JavaDoc cnonce;
358         final String JavaDoc digAlg = "MD5";
359         MessageDigest JavaDoc md5Helper;
360
361         try {
362             md5Helper = MessageDigest.getInstance(digAlg);
363         } catch (Exception JavaDoc e) {
364             throw new AuthenticationException(
365               "Unsupported algorithm in HTTP Digest authentication: "
366                + digAlg);
367         }
368
369         cnonce = Long.toString(System.currentTimeMillis());
370         cnonce = encode(md5Helper.digest(HttpConstants.getBytes(cnonce)));
371
372         return cnonce;
373     }
374 }
375
Popular Tags