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