1
--- trunk/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012/09/04 19:47:42 1380828
2
+++ trunk/java/org/apache/catalina/authenticator/DigestAuthenticator.java 2012/09/04 19:48:27 1380829
5
import java.util.StringTokenizer;
7
+import javax.servlet.http.HttpServletRequest;
8
import javax.servlet.http.HttpServletResponse;
11
import org.apache.catalina.LifecycleException;
12
import org.apache.catalina.Realm;
13
import org.apache.catalina.connector.Request;
16
public DigestAuthenticator() {
20
if (md5Helper == null)
21
md5Helper = MessageDigest.getInstance("MD5");
26
- * List of client nonce values currently being tracked
27
+ * List of server nonce values currently being tracked
29
- protected Map<String,NonceInfo> cnonces;
30
+ protected Map<String,NonceInfo> nonces;
34
- * Maximum number of client nonces to keep in the cache. If not specified,
35
+ * Maximum number of server nonces to keep in the cache. If not specified,
36
* the default value of 1000 is used.
38
- protected int cnonceCacheSize = 1000;
39
+ protected int nonceCacheSize = 1000;
47
- public int getCnonceCacheSize() {
48
- return cnonceCacheSize;
49
+ public int getNonceCacheSize() {
50
+ return nonceCacheSize;
54
- public void setCnonceCacheSize(int cnonceCacheSize) {
55
- this.cnonceCacheSize = cnonceCacheSize;
56
+ public void setNonceCacheSize(int nonceCacheSize) {
57
+ this.nonceCacheSize = nonceCacheSize;
62
// Validate any credentials already included with this request
63
String authorization = request.getHeader("authorization");
64
DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
65
- getKey(), cnonces, isValidateUri());
66
+ getKey(), nonces, isValidateUri());
67
if (authorization != null) {
68
- if (digestInfo.validate(request, authorization, config)) {
69
- principal = digestInfo.authenticate(context.getRealm());
71
+ if (digestInfo.parse(request, authorization)) {
72
+ if (digestInfo.validate(request, config)) {
73
+ principal = digestInfo.authenticate(context.getRealm());
76
- if (principal != null) {
77
- String username = parseUsername(authorization);
78
- register(request, response, principal,
79
- Constants.DIGEST_METHOD,
82
+ if (principal != null && !digestInfo.isNonceStale()) {
83
+ register(request, response, principal,
84
+ HttpServletRequest.DIGEST_AUTH,
85
+ digestInfo.getUsername(), null);
92
String nonce = generateNonce(request);
94
setAuthenticateHeader(request, response, config, nonce,
95
- digestInfo.isNonceStale());
96
+ principal != null && digestInfo.isNonceStale());
97
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
98
- // hres.flushBuffer();
105
* can be identified, return <code>null</code>
107
* @param authorization Authorization string to be parsed
109
+ * @deprecated Unused. Will be removed in Tomcat 8.0.x
112
protected String parseUsername(String authorization) {
114
// Validate the authorization credentials format
116
} else if (quotedString.length() > 2) {
117
return quotedString.substring(1, quotedString.length() - 1);
119
- return new String();
125
buffer = md5Helper.digest(ipTimeKey.getBytes());
128
- return currentTime + ":" + md5Encoder.encode(buffer);
129
+ String nonce = currentTime + ":" + md5Encoder.encode(buffer);
131
+ NonceInfo info = new NonceInfo(currentTime, 100);
132
+ synchronized (nonces) {
133
+ nonces.put(nonce, info);
141
setOpaque(generateSessionId());
144
- cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
145
+ nonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
147
private static final long serialVersionUID = 1L;
148
private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
150
Map.Entry<String,NonceInfo> eldest) {
151
// This is called from a sync so keep it simple
152
long currentTime = System.currentTimeMillis();
153
- if (size() > getCnonceCacheSize()) {
154
+ if (size() > getNonceCacheSize()) {
155
if (lastLog < currentTime &&
156
currentTime - eldest.getValue().getTimestamp() <
157
getNonceValidity()) {
158
@@ -480,10 +491,10 @@
160
private static class DigestInfo {
162
- private String opaque;
163
- private long nonceValidity;
164
- private String key;
165
- private Map<String,NonceInfo> cnonces;
166
+ private final String opaque;
167
+ private final long nonceValidity;
168
+ private final String key;
169
+ private final Map<String,NonceInfo> nonces;
170
private boolean validateUri = true;
172
private String userName = null;
173
@@ -495,21 +506,27 @@
174
private String cnonce = null;
175
private String realmName = null;
176
private String qop = null;
177
+ private String opaqueReceived = null;
179
private boolean nonceStale = false;
182
public DigestInfo(String opaque, long nonceValidity, String key,
183
- Map<String,NonceInfo> cnonces, boolean validateUri) {
184
+ Map<String,NonceInfo> nonces, boolean validateUri) {
185
this.opaque = opaque;
186
this.nonceValidity = nonceValidity;
188
- this.cnonces = cnonces;
189
+ this.nonces = nonces;
190
this.validateUri = validateUri;
193
- public boolean validate(Request request, String authorization,
194
- LoginConfig config) {
196
+ public String getUsername() {
201
+ public boolean parse(Request request, String authorization) {
202
// Validate the authorization credentials format
203
if (authorization == null) {
206
String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
208
method = request.getMethod();
209
- String opaque = null;
211
for (int i = 0; i < tokens.length; i++) {
212
String currentToken = tokens[i];
214
if ("response".equals(currentTokenName))
215
response = removeQuotes(currentTokenValue);
216
if ("opaque".equals(currentTokenName))
217
- opaque = removeQuotes(currentTokenValue);
218
+ opaqueReceived = removeQuotes(currentTokenValue);
224
+ public boolean validate(Request request, LoginConfig config) {
225
if ( (userName == null) || (realmName == null) || (nonce == null)
226
|| (uri == null) || (response == null) ) {
229
uriQuery = request.getRequestURI() + "?" + query;
231
if (!uri.equals(uriQuery)) {
233
+ // Some clients (older Android) use an absolute URI for
234
+ // DIGEST but a relative URI in the request line.
235
+ // request. 2.3.5 < fixed Android version <= 4.0.3
236
+ String host = request.getHeader("host");
237
+ String scheme = request.getScheme();
238
+ if (host != null && !uriQuery.startsWith(scheme)) {
239
+ StringBuilder absolute = new StringBuilder();
240
+ absolute.append(scheme);
241
+ absolute.append("://");
242
+ absolute.append(host);
243
+ absolute.append(uriQuery);
244
+ if (!uri.equals(absolute.toString())) {
256
// Validate the opaque string
257
- if (!this.opaque.equals(opaque)) {
258
+ if (!opaque.equals(opaqueReceived)) {
263
long currentTime = System.currentTimeMillis();
264
if ((currentTime - nonceTime) > nonceValidity) {
267
+ synchronized (nonces) {
268
+ nonces.remove(nonce);
271
String serverIpTimeKey =
272
request.getRemoteAddr() + ":" + nonceTime + ":" + key;
276
// Validate cnonce and nc
277
- // Check if presence of nc and nonce is consistent with presence of qop
278
+ // Check if presence of nc and Cnonce is consistent with presence of qop
280
if (cnonce != null || nc != null) {
283
if (cnonce == null || nc == null) {
286
- if (nc.length() != 8) {
287
+ // RFC 2617 says nc must be 8 digits long. Older Android clients
288
+ // use 6. 2.3.5 < fixed Android version <= 4.0.3
289
+ if (nc.length() < 6 || nc.length() > 8) {
293
@@ -644,21 +684,18 @@
297
- synchronized (cnonces) {
298
- info = cnonces.get(cnonce);
299
+ synchronized (nonces) {
300
+ info = nonces.get(nonce);
303
- info = new NonceInfo();
304
+ // Nonce is valid but not in cache. It must have dropped out
305
+ // of the cache - force a re-authentication
308
- if (count <= info.getCount()) {
309
+ if (!info.nonceCountValid(count)) {
313
- info.setCount(count);
314
- info.setTimestamp(currentTime);
315
- synchronized (cnonces) {
316
- cnonces.put(cnonce, info);
321
@@ -685,19 +722,31 @@
324
private static class NonceInfo {
325
- private volatile long count;
326
private volatile long timestamp;
328
- public void setCount(long l) {
330
+ private volatile boolean seen[];
331
+ private volatile int offset;
332
+ private volatile int count = 0;
334
+ public NonceInfo(long currentTime, int seenWindowSize) {
335
+ this.timestamp = currentTime;
336
+ seen = new boolean[seenWindowSize];
337
+ offset = seenWindowSize / 2;
340
- public long getCount() {
344
- public void setTimestamp(long l) {
346
+ public synchronized boolean nonceCountValid(long nonceCount) {
347
+ if ((count - offset) >= nonceCount ||
348
+ (nonceCount > count - offset + seen.length)) {
351
+ int checkIndex = (int) ((nonceCount + offset) % seen.length);
352
+ if (seen[checkIndex]) {
355
+ seen[checkIndex] = true;
356
+ seen[count % seen.length] = false;
362
public long getTimestamp() {