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 case ' ':
119 break;
120
121 default:
122 last = tok;
123 if (name != null)
124 {
125 if ("username".equalsIgnoreCase(name))
126 digest.username = tok;
127 else if ("realm".equalsIgnoreCase(name))
128 digest.realm = tok;
129 else if ("nonce".equalsIgnoreCase(name))
130 digest.nonce = tok;
131 else if ("nc".equalsIgnoreCase(name))
132 digest.nc = tok;
133 else if ("cnonce".equalsIgnoreCase(name))
134 digest.cnonce = tok;
135 else if ("qop".equalsIgnoreCase(name))
136 digest.qop = tok;
137 else if ("uri".equalsIgnoreCase(name))
138 digest.uri = tok;
139 else if ("response".equalsIgnoreCase(name))
140 digest.response = tok;
141 name=null;
142 }
143 }
144 }
145
146 int n = checkNonce(digest.nonce, (Request)request);
147
148 if (n > 0)
149 {
150 UserIdentity user = _loginService.login(digest.username,digest);
151 if (user!=null)
152 {
153 renewSessionOnAuthentication(request,response);
154 return new UserAuthentication(getAuthMethod(),user);
155 }
156 }
157 else if (n == 0)
158 stale = true;
159
160 }
161
162 if (!_deferred.isDeferred(response))
163 {
164 String domain = request.getContextPath();
165 if (domain == null)
166 domain = "/";
167 response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"" + _loginService.getName()
168 + "\", domain=\""
169 + domain
170 + "\", nonce=\""
171 + newNonce((Request)request)
172 + "\", algorithm=MD5, qop=\"auth\""
173 + (_useStale ? (" stale=" + stale) : ""));
174 response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
175
176 return Authentication.SEND_CONTINUE;
177 }
178
179 return Authentication.UNAUTHENTICATED;
180 }
181 catch (IOException e)
182 {
183 throw new ServerAuthException(e);
184 }
185
186 }
187
188 public String newNonce(Request request)
189 {
190 long ts=request.getTimeStamp();
191 long sk = _nonceSecret;
192
193 byte[] nounce = new byte[24];
194 for (int i = 0; i < 8; i++)
195 {
196 nounce[i] = (byte) (ts & 0xff);
197 ts = ts >> 8;
198 nounce[8 + i] = (byte) (sk & 0xff);
199 sk = sk >> 8;
200 }
201
202 byte[] hash = null;
203 try
204 {
205 MessageDigest md = MessageDigest.getInstance("MD5");
206 md.reset();
207 md.update(nounce, 0, 16);
208 hash = md.digest();
209 }
210 catch (Exception e)
211 {
212 LOG.warn(e);
213 }
214
215 for (int i = 0; i < hash.length; i++)
216 {
217 nounce[8 + i] = hash[i];
218 if (i == 23) break;
219 }
220
221 return new String(B64Code.encode(nounce));
222 }
223
224
225
226
227
228
229
230 private int checkNonce(String nonce, Request request)
231 {
232 try
233 {
234 byte[] n = B64Code.decode(nonce.toCharArray());
235 if (n.length != 24) return -1;
236
237 long ts = 0;
238 long sk = _nonceSecret;
239 byte[] n2 = new byte[16];
240 System.arraycopy(n, 0, n2, 0, 8);
241 for (int i = 0; i < 8; i++)
242 {
243 n2[8 + i] = (byte) (sk & 0xff);
244 sk = sk >> 8;
245 ts = (ts << 8) + (0xff & (long) n[7 - i]);
246 }
247
248 long age = request.getTimeStamp() - ts;
249 if (LOG.isDebugEnabled()) LOG.debug("age=" + age);
250
251 byte[] hash = null;
252 try
253 {
254 MessageDigest md = MessageDigest.getInstance("MD5");
255 md.reset();
256 md.update(n2, 0, 16);
257 hash = md.digest();
258 }
259 catch (Exception e)
260 {
261 LOG.warn(e);
262 }
263
264 for (int i = 0; i < 16; i++)
265 if (n[i + 8] != hash[i]) return -1;
266
267 if (_maxNonceAge > 0 && (age < 0 || age > _maxNonceAge)) return 0;
268
269 return 1;
270 }
271 catch (Exception e)
272 {
273 LOG.ignore(e);
274 }
275 return -1;
276 }
277
278 private static class Digest extends Credential
279 {
280 private static final long serialVersionUID = -2484639019549527724L;
281 String method = null;
282 String username = null;
283 String realm = null;
284 String nonce = null;
285 String nc = null;
286 String cnonce = null;
287 String qop = null;
288 String uri = null;
289 String response = null;
290
291
292 Digest(String m)
293 {
294 method = m;
295 }
296
297
298 @Override
299 public boolean check(Object credentials)
300 {
301 if (credentials instanceof char[])
302 credentials=new String((char[])credentials);
303 String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
304
305 try
306 {
307 MessageDigest md = MessageDigest.getInstance("MD5");
308 byte[] ha1;
309 if (credentials instanceof Credential.MD5)
310 {
311
312
313
314 ha1 = ((Credential.MD5) credentials).getDigest();
315 }
316 else
317 {
318
319 md.update(username.getBytes(StringUtil.__ISO_8859_1));
320 md.update((byte) ':');
321 md.update(realm.getBytes(StringUtil.__ISO_8859_1));
322 md.update((byte) ':');
323 md.update(password.getBytes(StringUtil.__ISO_8859_1));
324 ha1 = md.digest();
325 }
326
327 md.reset();
328 md.update(method.getBytes(StringUtil.__ISO_8859_1));
329 md.update((byte) ':');
330 md.update(uri.getBytes(StringUtil.__ISO_8859_1));
331 byte[] ha2 = md.digest();
332
333
334
335
336
337
338
339
340 md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
341 md.update((byte) ':');
342 md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
343 md.update((byte) ':');
344 md.update(nc.getBytes(StringUtil.__ISO_8859_1));
345 md.update((byte) ':');
346 md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
347 md.update((byte) ':');
348 md.update(qop.getBytes(StringUtil.__ISO_8859_1));
349 md.update((byte) ':');
350 md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
351 byte[] digest = md.digest();
352
353
354 return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
355 }
356 catch (Exception e)
357 {
358 LOG.warn(e);
359 }
360
361 return false;
362 }
363
364 public String toString()
365 {
366 return username + "," + response;
367 }
368 }
369 }