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