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