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