1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.security.jaspi.modules;
20
21 import java.io.IOException;
22 import java.nio.charset.StandardCharsets;
23 import java.security.MessageDigest;
24 import java.util.Map;
25
26 import javax.security.auth.Subject;
27 import javax.security.auth.callback.CallbackHandler;
28 import javax.security.auth.callback.UnsupportedCallbackException;
29 import javax.security.auth.message.AuthException;
30 import javax.security.auth.message.AuthStatus;
31 import javax.security.auth.message.MessageInfo;
32 import javax.security.auth.message.MessagePolicy;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35
36 import org.eclipse.jetty.http.HttpHeader;
37 import org.eclipse.jetty.util.B64Code;
38 import org.eclipse.jetty.util.QuotedStringTokenizer;
39 import org.eclipse.jetty.util.TypeUtil;
40 import org.eclipse.jetty.util.log.Log;
41 import org.eclipse.jetty.util.log.Logger;
42 import org.eclipse.jetty.util.security.Constraint;
43 import org.eclipse.jetty.util.security.Credential;
44
45 @Deprecated
46 public class DigestAuthModule extends BaseAuthModule
47 {
48 private static final Logger LOG = Log.getLogger(DigestAuthModule.class);
49
50 protected long maxNonceAge = 0;
51
52 protected long nonceSecret = this.hashCode() ^ System.currentTimeMillis();
53
54 protected boolean useStale = false;
55
56 private String realmName;
57
58 private static final String REALM_KEY = "org.eclipse.jetty.security.jaspi.modules.RealmName";
59
60 public DigestAuthModule()
61 {
62 }
63
64 public DigestAuthModule(CallbackHandler callbackHandler, String realmName)
65 {
66 super(callbackHandler);
67 this.realmName = realmName;
68 }
69
70 @Override
71 public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy,
72 CallbackHandler handler, Map options)
73 throws AuthException
74 {
75 super.initialize(requestPolicy, responsePolicy, handler, options);
76 realmName = (String) options.get(REALM_KEY);
77 }
78
79 @Override
80 public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject,
81 Subject serviceSubject)
82 throws AuthException
83 {
84 HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
85 HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
86 String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
87
88 try
89 {
90 boolean stale = false;
91
92 long timestamp = System.currentTimeMillis();
93 if (credentials != null)
94 {
95 if (LOG.isDebugEnabled()) LOG.debug("Credentials: " + credentials);
96 QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
97 final Digest digest = new Digest(request.getMethod());
98 String last = null;
99 String name = null;
100
101 while (tokenizer.hasMoreTokens())
102 {
103 String tok = tokenizer.nextToken();
104 char c = (tok.length() == 1) ? tok.charAt(0) : '\0';
105
106 switch (c)
107 {
108 case '=':
109 name = last;
110 last = tok;
111 break;
112 case ',':
113 name = null;
114 case ' ':
115 break;
116
117 default:
118 last = tok;
119 if (name != null)
120 {
121 if ("username".equalsIgnoreCase(name))
122 digest.username = tok;
123 else if ("realm".equalsIgnoreCase(name))
124 digest.realm = tok;
125 else if ("nonce".equalsIgnoreCase(name))
126 digest.nonce = tok;
127 else if ("nc".equalsIgnoreCase(name))
128 digest.nc = tok;
129 else if ("cnonce".equalsIgnoreCase(name))
130 digest.cnonce = tok;
131 else if ("qop".equalsIgnoreCase(name))
132 digest.qop = tok;
133 else if ("uri".equalsIgnoreCase(name))
134 digest.uri = tok;
135 else if ("response".equalsIgnoreCase(name)) digest.response = tok;
136 break;
137 }
138 }
139 }
140
141 int n = checkNonce(digest.nonce, timestamp);
142
143 if (n > 0)
144 {
145 if (login(clientSubject, digest.username, digest, Constraint.__DIGEST_AUTH, messageInfo)) { return AuthStatus.SUCCESS; }
146 }
147 else if (n == 0) stale = true;
148
149 }
150
151 if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; }
152 String domain = request.getContextPath();
153 if (domain == null) domain = "/";
154 response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + realmName
155 + "\", domain=\""
156 + domain
157 + "\", nonce=\""
158 + newNonce(timestamp)
159 + "\", algorithm=MD5, qop=\"auth\""
160 + (useStale ? (" stale=" + stale) : ""));
161 response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
162 return AuthStatus.SEND_CONTINUE;
163 }
164 catch (IOException e)
165 {
166 throw new AuthException(e.getMessage());
167 }
168 catch (UnsupportedCallbackException e)
169 {
170 throw new AuthException(e.getMessage());
171 }
172
173 }
174
175 public String newNonce(long ts)
176 {
177
178 long sk = nonceSecret;
179
180 byte[] nounce = new byte[24];
181 for (int i = 0; i < 8; i++)
182 {
183 nounce[i] = (byte) (ts & 0xff);
184 ts = ts >> 8;
185 nounce[8 + i] = (byte) (sk & 0xff);
186 sk = sk >> 8;
187 }
188
189 byte[] hash = null;
190 try
191 {
192 MessageDigest md = MessageDigest.getInstance("MD5");
193 md.reset();
194 md.update(nounce, 0, 16);
195 hash = md.digest();
196 }
197 catch (Exception e)
198 {
199 LOG.warn(e);
200 }
201
202 for (int i = 0; i < hash.length; i++)
203 {
204 nounce[8 + i] = hash[i];
205 if (i == 23) break;
206 }
207
208 return new String(B64Code.encode(nounce));
209 }
210
211
212
213
214
215
216 public int checkNonce(String nonce, long timestamp)
217 {
218 try
219 {
220 byte[] n = B64Code.decode(nonce.toCharArray());
221 if (n.length != 24) return -1;
222
223 long ts = 0;
224 long sk = nonceSecret;
225 byte[] n2 = new byte[16];
226 System.arraycopy(n, 0, n2, 0, 8);
227 for (int i = 0; i < 8; i++)
228 {
229 n2[8 + i] = (byte) (sk & 0xff);
230 sk = sk >> 8;
231 ts = (ts << 8) + (0xff & (long) n[7 - i]);
232 }
233
234 long age = timestamp - ts;
235 if (LOG.isDebugEnabled()) LOG.debug("age=" + age);
236
237 byte[] hash = null;
238 try
239 {
240 MessageDigest md = MessageDigest.getInstance("MD5");
241 md.reset();
242 md.update(n2, 0, 16);
243 hash = md.digest();
244 }
245 catch (Exception e)
246 {
247 LOG.warn(e);
248 }
249
250 for (int i = 0; i < 16; i++)
251 if (n[i + 8] != hash[i]) return -1;
252
253 if (maxNonceAge > 0 && (age < 0 || age > maxNonceAge)) return 0;
254
255 return 1;
256 }
257 catch (Exception e)
258 {
259 LOG.ignore(e);
260 }
261 return -1;
262 }
263
264 private static class Digest extends Credential
265 {
266 private static final long serialVersionUID = -1866670896275159116L;
267
268 String method = null;
269 String username = null;
270 String realm = null;
271 String nonce = null;
272 String nc = null;
273 String cnonce = null;
274 String qop = null;
275 String uri = null;
276 String response = null;
277
278
279 Digest(String m)
280 {
281 method = m;
282 }
283
284
285 @Override
286 public boolean check(Object credentials)
287 {
288 String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
289
290 try
291 {
292 MessageDigest md = MessageDigest.getInstance("MD5");
293 byte[] ha1;
294 if (credentials instanceof Credential.MD5)
295 {
296
297
298
299 ha1 = ((Credential.MD5) credentials).getDigest();
300 }
301 else
302 {
303
304 md.update(username.getBytes(StandardCharsets.ISO_8859_1));
305 md.update((byte) ':');
306 md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
307 md.update((byte) ':');
308 md.update(password.getBytes(StandardCharsets.ISO_8859_1));
309 ha1 = md.digest();
310 }
311
312 md.reset();
313 md.update(method.getBytes(StandardCharsets.ISO_8859_1));
314 md.update((byte) ':');
315 md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
316 byte[] ha2 = md.digest();
317
318
319
320
321
322
323
324
325 md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
326 md.update((byte) ':');
327 md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
328 md.update((byte) ':');
329 md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
330 md.update((byte) ':');
331 md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
332 md.update((byte) ':');
333 md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
334 md.update((byte) ':');
335 md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
336 byte[] digest = md.digest();
337
338
339 return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
340 }
341 catch (Exception e)
342 {
343 LOG.warn(e);
344 }
345
346 return false;
347 }
348
349 @Override
350 public String toString()
351 {
352 return username + "," + response;
353 }
354
355 }
356 }