1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.mortbay.jetty.security;
16
17 import java.io.IOException;
18 import java.security.MessageDigest;
19 import java.security.Principal;
20
21 import javax.servlet.http.HttpServletResponse;
22
23 import org.mortbay.jetty.Authenticator;
24 import org.mortbay.jetty.HttpHeaders;
25 import org.mortbay.jetty.Request;
26 import org.mortbay.jetty.Response;
27 import org.mortbay.jetty.UserRealm;
28 import org.mortbay.log.Log;
29 import org.mortbay.util.QuotedStringTokenizer;
30 import org.mortbay.util.StringUtil;
31 import org.mortbay.util.TypeUtil;
32
33
34
35
36
37
38 public class DigestAuthenticator implements Authenticator
39 {
40 protected long maxNonceAge=0;
41 protected long nonceSecret=this.hashCode() ^ System.currentTimeMillis();
42 protected boolean useStale=false;
43
44
45
46
47
48
49
50
51
52 public Principal authenticate(UserRealm realm,
53 String pathInContext,
54 Request request,
55 Response response)
56 throws IOException
57 {
58
59 boolean stale=false;
60 Principal user=null;
61 String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
62
63 if (credentials!=null )
64 {
65 if(Log.isDebugEnabled())Log.debug("Credentials: "+credentials);
66 QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials,
67 "=, ",
68 true,
69 false);
70 Digest digest=new Digest(request.getMethod());
71 String last=null;
72 String name=null;
73
74 loop:
75 while (tokenizer.hasMoreTokens())
76 {
77 String tok = tokenizer.nextToken();
78 char c=(tok.length()==1)?tok.charAt(0):'\0';
79
80 switch (c)
81 {
82 case '=':
83 name=last;
84 last=tok;
85 break;
86 case ',':
87 name=null;
88 case ' ':
89 break;
90
91 default:
92 last=tok;
93 if (name!=null)
94 {
95 if ("username".equalsIgnoreCase(name))
96 digest.username=tok;
97 else if ("realm".equalsIgnoreCase(name))
98 digest.realm=tok;
99 else if ("nonce".equalsIgnoreCase(name))
100 digest.nonce=tok;
101 else if ("nc".equalsIgnoreCase(name))
102 digest.nc=tok;
103 else if ("cnonce".equalsIgnoreCase(name))
104 digest.cnonce=tok;
105 else if ("qop".equalsIgnoreCase(name))
106 digest.qop=tok;
107 else if ("uri".equalsIgnoreCase(name))
108 digest.uri=tok;
109 else if ("response".equalsIgnoreCase(name))
110 digest.response=tok;
111 break;
112 }
113 }
114 }
115
116 int n=checkNonce(digest.nonce,request);
117 if (n>0)
118 user = realm.authenticate(digest.username,digest,request);
119 else if (n==0)
120 stale = true;
121
122 if (user==null)
123 Log.warn("AUTH FAILURE: user "+StringUtil.printable(digest.username));
124 else
125 {
126 request.setAuthType(Constraint.__DIGEST_AUTH);
127 request.setUserPrincipal(user);
128 }
129 }
130
131
132 if (user==null && response!=null)
133 sendChallenge(realm,request,response,stale);
134
135 return user;
136 }
137
138
139 public String getAuthMethod()
140 {
141 return Constraint.__DIGEST_AUTH;
142 }
143
144
145 public void sendChallenge(UserRealm realm,
146 Request request,
147 Response response,
148 boolean stale)
149 throws IOException
150 {
151 String domain=request.getContextPath();
152 if (domain==null)
153 domain="/";
154 response.setHeader(HttpHeaders.WWW_AUTHENTICATE,
155 "Digest realm=\""+realm.getName()+
156 "\", domain=\""+domain +
157 "\", nonce=\""+newNonce(request)+
158 "\", algorithm=MD5, qop=\"auth\"" + (useStale?(" stale="+stale):"")
159 );
160 response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
161 }
162
163
164 public String newNonce(Request request)
165 {
166 long ts=request.getTimeStamp();
167 long sk=nonceSecret;
168
169 byte[] nounce = new byte[24];
170 for (int i=0;i<8;i++)
171 {
172 nounce[i]=(byte)(ts&0xff);
173 ts=ts>>8;
174 nounce[8+i]=(byte)(sk&0xff);
175 sk=sk>>8;
176 }
177
178 byte[] hash=null;
179 try
180 {
181 MessageDigest md = MessageDigest.getInstance("MD5");
182 md.reset();
183 md.update(nounce,0,16);
184 hash = md.digest();
185 }
186 catch(Exception e)
187 {
188 Log.warn(e);
189 }
190
191 for (int i=0;i<hash.length;i++)
192 {
193 nounce[8+i]=hash[i];
194 if (i==23)
195 break;
196 }
197
198 return new String(B64Code.encode(nounce));
199 }
200
201
202
203
204
205
206
207 public int checkNonce(String nonce, Request request)
208 {
209 try
210 {
211 byte[] n = B64Code.decode(nonce.toCharArray());
212 if (n.length!=24)
213 return -1;
214
215 long ts=0;
216 long sk=nonceSecret;
217 byte[] n2 = new byte[16];
218 System.arraycopy(n, 0, n2, 0, 8);
219 for (int i=0;i<8;i++)
220 {
221 n2[8+i]=(byte)(sk&0xff);
222 sk=sk>>8;
223 ts=(ts<<8)+(0xff&(long)n[7-i]);
224 }
225
226 long age=request.getTimeStamp()-ts;
227 if (Log.isDebugEnabled()) Log.debug("age="+age);
228
229 byte[] hash=null;
230 try
231 {
232 MessageDigest md = MessageDigest.getInstance("MD5");
233 md.reset();
234 md.update(n2,0,16);
235 hash = md.digest();
236 }
237 catch(Exception e)
238 {
239 Log.warn(e);
240 }
241
242 for (int i=0;i<16;i++)
243 if (n[i+8]!=hash[i])
244 return -1;
245
246 if(maxNonceAge>0 && (age<0 || age>maxNonceAge))
247 return 0;
248
249 return 1;
250 }
251 catch(Exception e)
252 {
253 Log.ignore(e);
254 }
255 return -1;
256 }
257
258
259
260
261 private static class Digest extends Credential
262 {
263 String method=null;
264 String username = null;
265 String realm = null;
266 String nonce = null;
267 String nc = null;
268 String cnonce = null;
269 String qop = null;
270 String uri = null;
271 String response=null;
272
273
274 Digest(String m)
275 {
276 method=m;
277 }
278
279
280 public boolean check(Object credentials)
281 {
282 String password=(credentials instanceof String)
283 ?(String)credentials
284 :credentials.toString();
285
286 try{
287 MessageDigest md = MessageDigest.getInstance("MD5");
288 byte[] ha1;
289 if(credentials instanceof Credential.MD5)
290 {
291
292
293
294 ha1 = ((Credential.MD5)credentials).getDigest();
295 }
296 else
297 {
298
299 md.update(username.getBytes(StringUtil.__ISO_8859_1));
300 md.update((byte)':');
301 md.update(realm.getBytes(StringUtil.__ISO_8859_1));
302 md.update((byte)':');
303 md.update(password.getBytes(StringUtil.__ISO_8859_1));
304 ha1=md.digest();
305 }
306
307 md.reset();
308 md.update(method.getBytes(StringUtil.__ISO_8859_1));
309 md.update((byte)':');
310 md.update(uri.getBytes(StringUtil.__ISO_8859_1));
311 byte[] ha2=md.digest();
312
313
314
315
316
317
318
319
320
321
322
323 md.update(TypeUtil.toString(ha1,16).getBytes(StringUtil.__ISO_8859_1));
324 md.update((byte)':');
325 md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
326 md.update((byte)':');
327 md.update(nc.getBytes(StringUtil.__ISO_8859_1));
328 md.update((byte)':');
329 md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
330 md.update((byte)':');
331 md.update(qop.getBytes(StringUtil.__ISO_8859_1));
332 md.update((byte)':');
333 md.update(TypeUtil.toString(ha2,16).getBytes(StringUtil.__ISO_8859_1));
334 byte[] digest=md.digest();
335
336
337 return (TypeUtil.toString(digest,16).equalsIgnoreCase(response));
338 }
339 catch (Exception e)
340 {Log.warn(e);}
341
342 return false;
343 }
344
345 public String toString()
346 {
347 return username+","+response;
348 }
349
350 }
351
352
353
354 public long getMaxNonceAge()
355 {
356 return maxNonceAge;
357 }
358
359
360
361 public void setMaxNonceAge(long maxNonceAge)
362 {
363 this.maxNonceAge = maxNonceAge;
364 }
365
366
367
368 public long getNonceSecret()
369 {
370 return nonceSecret;
371 }
372
373
374
375 public void setNonceSecret(long nonceSecret)
376 {
377 this.nonceSecret = nonceSecret;
378 }
379
380 public void setUseStale(boolean us)
381 {
382 this.useStale=us;
383 }
384
385 public boolean getUseStale()
386 {
387 return useStale;
388 }
389 }
390