KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mortbay > http > DigestAuthenticator


1 // ========================================================================
2
// $Id: DigestAuthenticator.java,v 1.16 2005/08/13 00:01:24 gregwilkins Exp $
3
// Copyright 2002-2004 Mort Bay Consulting Pty. Ltd.
4
// ------------------------------------------------------------------------
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
// ========================================================================
15

16 package org.mortbay.http;
17
18 import java.io.IOException JavaDoc;
19 import java.security.MessageDigest JavaDoc;
20 import java.security.Principal JavaDoc;
21
22 import org.apache.commons.logging.Log;
23 import org.mortbay.log.LogFactory;
24 import org.mortbay.util.B64Code;
25 import org.mortbay.util.Credential;
26 import org.mortbay.util.LogSupport;
27 import org.mortbay.util.QuotedStringTokenizer;
28 import org.mortbay.util.StringUtil;
29 import org.mortbay.util.TypeUtil;
30
31 /* ------------------------------------------------------------ */
32 /** DIGEST authentication.
33  *
34  * @version $Id: DigestAuthenticator.java,v 1.16 2005/08/13 00:01:24 gregwilkins Exp $
35  * @author Greg Wilkins (gregw)
36  */

37 public class DigestAuthenticator implements Authenticator
38 {
39     static Log log = LogFactory.getLog(DigestAuthenticator.class);
40
41     protected long maxNonceAge=0;
42     protected long nonceSecret=this.hashCode() ^ System.currentTimeMillis();
43     protected boolean useStale=false;
44     
45     
46     /* ------------------------------------------------------------ */
47     /**
48      * @return UserPrinciple if authenticated or null if not. If
49      * Authentication fails, then the authenticator may have committed
50      * the response as an auth challenge or redirect.
51      * @exception IOException
52      */

53     public Principal JavaDoc authenticate(UserRealm realm,
54                                            String JavaDoc pathInContext,
55                                            HttpRequest request,
56                                            HttpResponse response)
57         throws IOException JavaDoc
58     {
59         // Get the user if we can
60
boolean stale=false;
61         Principal JavaDoc user=null;
62         String JavaDoc credentials = request.getField(HttpFields.__Authorization);
63         
64         if (credentials!=null )
65         {
66             if(log.isDebugEnabled())log.debug("Credentials: "+credentials);
67             QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials,
68                                                                         "=, ",
69                                                                         true,
70                                                                         false);
71             Digest JavaDoc digest=new Digest JavaDoc(request.getMethod());
72             String JavaDoc last=null;
73             String JavaDoc name=null;
74
75           loop:
76             while (tokenizer.hasMoreTokens())
77             {
78                 String JavaDoc tok = tokenizer.nextToken();
79                 char c=(tok.length()==1)?tok.charAt(0):'\0';
80
81                 switch (c)
82                 {
83                   case '=':
84                       name=last;
85                       last=tok;
86                       break;
87                   case ',':
88                       name=null;
89                   case ' ':
90                       break;
91
92                   default:
93                       last=tok;
94                       if (name!=null)
95                       {
96                           if ("username".equalsIgnoreCase(name))
97                               digest.username=tok;
98                           else if ("realm".equalsIgnoreCase(name))
99                               digest.realm=tok;
100                           else if ("nonce".equalsIgnoreCase(name))
101                               digest.nonce=tok;
102                           else if ("nc".equalsIgnoreCase(name))
103                               digest.nc=tok;
104                           else if ("cnonce".equalsIgnoreCase(name))
105                               digest.cnonce=tok;
106                           else if ("qop".equalsIgnoreCase(name))
107                               digest.qop=tok;
108                           else if ("uri".equalsIgnoreCase(name))
109                               digest.uri=tok;
110                           else if ("response".equalsIgnoreCase(name))
111                               digest.response=tok;
112                           break;
113                       }
114                 }
115             }
116
117             int n=checkNonce(digest.nonce,request);
118             if (n>0)
119                 user = realm.authenticate(digest.username,digest,request);
120             else if (n==0)
121                 stale = true;
122             
123             if (user==null)
124                 log.warn("AUTH FAILURE: user "+digest.username);
125             else
126             {
127                 request.setAuthType(SecurityConstraint.__DIGEST_AUTH);
128                 request.setAuthUser(digest.username);
129                 request.setUserPrincipal(user);
130             }
131         }
132
133         // Challenge if we have no user
134
if (user==null && response!=null)
135             sendChallenge(realm,request,response,stale);
136         
137         return user;
138     }
139     
140     /* ------------------------------------------------------------ */
141     public String JavaDoc getAuthMethod()
142     {
143         return SecurityConstraint.__DIGEST_AUTH;
144     }
145     
146     /* ------------------------------------------------------------ */
147     public void sendChallenge(UserRealm realm,
148                               HttpRequest request,
149                               HttpResponse response,
150                               boolean stale)
151         throws IOException JavaDoc
152     {
153         response.setField(HttpFields.__WwwAuthenticate,
154                 "Digest realm=\""+realm.getName()+
155                 "\", domain=\""+
156                 response.getHttpContext().getContextPath() +
157                 "\", nonce=\""+newNonce(request)+
158                 "\", algorithm=MD5, qop=\"auth\"" + (useStale?(" stale="+stale):"")
159                           );
160         
161         response.sendError(HttpResponse.__401_Unauthorized);
162     }
163
164     /* ------------------------------------------------------------ */
165     public String JavaDoc newNonce(HttpRequest request)
166     {
167         long ts=request.getTimeStamp();
168         long sk=nonceSecret;
169         
170         byte[] nounce = new byte[24];
171         for (int i=0;i<8;i++)
172         {
173             nounce[i]=(byte)(ts&0xff);
174             ts=ts>>8;
175             nounce[8+i]=(byte)(sk&0xff);
176             sk=sk>>8;
177         }
178         
179         byte[] hash=null;
180         try
181         {
182             MessageDigest JavaDoc md = MessageDigest.getInstance("MD5");
183             md.reset();
184             md.update(nounce,0,16);
185             hash = md.digest();
186         }
187         catch(Exception JavaDoc e)
188         {
189             log.fatal(this,e);
190         }
191         
192         for (int i=0;i<hash.length;i++)
193         {
194             nounce[8+i]=hash[i];
195             if (i==23)
196                 break;
197         }
198         
199         return new String JavaDoc(B64Code.encode(nounce));
200     }
201
202     /**
203      * @param nonce
204      * @param request
205      * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
206      */

207     /* ------------------------------------------------------------ */
208     public int checkNonce(String JavaDoc nonce, HttpRequest request)
209     {
210         try
211         {
212             byte[] n = B64Code.decode(nonce.toCharArray());
213             if (n.length!=24)
214                 return -1;
215             
216             long ts=0;
217             long sk=nonceSecret;
218             byte[] n2 = new byte[16];
219             for (int i=0;i<8;i++)
220             {
221                 n2[i]=n[i];
222                 n2[8+i]=(byte)(sk&0xff);
223                 sk=sk>>8;
224                 ts=(ts<<8)+(0xff&(long)n[7-i]);
225             }
226             
227             long age=request.getTimeStamp()-ts;
228         if (log.isDebugEnabled()) log.debug("age="+age);
229             
230             byte[] hash=null;
231             try
232             {
233                 MessageDigest JavaDoc md = MessageDigest.getInstance("MD5");
234                 md.reset();
235                 md.update(n2,0,16);
236                 hash = md.digest();
237             }
238             catch(Exception JavaDoc e)
239             {
240                 log.fatal(this,e);
241             }
242             
243             for (int i=0;i<16;i++)
244                 if (n[i+8]!=hash[i])
245                     return -1;
246                 
247             if(maxNonceAge>0 && (age<0 || age>maxNonceAge))
248                 return 0; // stale
249

250             return 1;
251         }
252         catch(Exception JavaDoc e)
253         {
254             log.debug("",e);
255         }
256         return -1;
257     }
258
259     /* ------------------------------------------------------------ */
260     /* ------------------------------------------------------------ */
261     /* ------------------------------------------------------------ */
262     private static class Digest extends Credential
263     {
264         String JavaDoc method=null;
265         String JavaDoc username = null;
266         String JavaDoc realm = null;
267         String JavaDoc nonce = null;
268         String JavaDoc nc = null;
269         String JavaDoc cnonce = null;
270         String JavaDoc qop = null;
271         String JavaDoc uri = null;
272         String JavaDoc response=null;
273         
274         /* ------------------------------------------------------------ */
275         Digest(String JavaDoc m)
276         {
277             method=m;
278         }
279         
280         /* ------------------------------------------------------------ */
281         public boolean check(Object JavaDoc credentials)
282         {
283             String JavaDoc password=(credentials instanceof String JavaDoc)
284                 ?(String JavaDoc)credentials
285                 :credentials.toString();
286             
287             try{
288                 MessageDigest JavaDoc md = MessageDigest.getInstance("MD5");
289                 byte[] ha1;
290                 if(credentials instanceof Credential.MD5)
291                 {
292                     // Credentials are already a MD5 digest - assume it's in
293
// form user:realm:password (we have no way to know since
294
// it's a digest, alright?)
295
ha1 = ((Credential.MD5)credentials).getDigest();
296                 }
297                 else
298                 {
299                     // calc A1 digest
300
md.update(username.getBytes(StringUtil.__ISO_8859_1));
301                     md.update((byte)':');
302                     md.update(realm.getBytes(StringUtil.__ISO_8859_1));
303                     md.update((byte)':');
304                     md.update(password.getBytes(StringUtil.__ISO_8859_1));
305                     ha1=md.digest();
306                 }
307                 // calc A2 digest
308
md.reset();
309                 md.update(method.getBytes(StringUtil.__ISO_8859_1));
310                 md.update((byte)':');
311                 md.update(uri.getBytes(StringUtil.__ISO_8859_1));
312                 byte[] ha2=md.digest();
313                 
314                 
315                 
316                 
317                 
318                 // calc digest
319
// request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) ) <">
320
// request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
321

322                 
323                 
324                 md.update(TypeUtil.toString(ha1,16).getBytes(StringUtil.__ISO_8859_1));
325                 md.update((byte)':');
326                 md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
327                 md.update((byte)':');
328                 md.update(nc.getBytes(StringUtil.__ISO_8859_1));
329                 md.update((byte)':');
330                 md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
331                 md.update((byte)':');
332                 md.update(qop.getBytes(StringUtil.__ISO_8859_1));
333                 md.update((byte)':');
334                 md.update(TypeUtil.toString(ha2,16).getBytes(StringUtil.__ISO_8859_1));
335                 byte[] digest=md.digest();
336                 
337                 // check digest
338
return (TypeUtil.toString(digest,16).equalsIgnoreCase(response));
339             }
340             catch (Exception JavaDoc e)
341             {log.warn(LogSupport.EXCEPTION,e);}
342
343             return false;
344         }
345
346         public String JavaDoc toString()
347         {
348             return username+","+response;
349         }
350         
351     }
352     /**
353      * @return Returns the maxNonceAge.
354      */

355     public long getMaxNonceAge()
356     {
357         return maxNonceAge;
358     }
359     /**
360      * @param maxNonceAge The maxNonceAge to set.
361      */

362     public void setMaxNonceAge(long maxNonceAge)
363     {
364         this.maxNonceAge = maxNonceAge;
365     }
366     /**
367      * @return Returns the nonceSecret.
368      */

369     public long getNonceSecret()
370     {
371         return nonceSecret;
372     }
373     /**
374      * @param nonceSecret The nonceSecret to set.
375      */

376     public void setNonceSecret(long nonceSecret)
377     {
378         this.nonceSecret = nonceSecret;
379     }
380
381     public void setUseStale(boolean us)
382     {
383     this.useStale=us;
384     }
385
386     public boolean getUseStale()
387     {
388     return useStale;
389     }
390 }
391     
392
Popular Tags