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