~ubuntu-branches/ubuntu/wily/tomcat6/wily-proposed

« back to all changes in this revision

Viewing changes to java/org/apache/catalina/authenticator/DigestAuthenticator.java

  • Committer: Package Import Robot
  • Author(s): tony mancill, tony mancill, Niels Thykier
  • Date: 2011-11-08 10:42:32 UTC
  • Revision ID: package-import@ubuntu.com-20111108104232-qu3ebmbg5tteonpq
Tags: 6.0.32-7
[ tony mancill ]
* Team upload.
* Add "unset LC_ALL" to /etc/defaults/tomcat6 to prevent user 
  environment settings from leaking into the servlet container.
  - Thank you to Nicolas Pichon.  (Closes: #645221)
* Apply patch for CVE-2011-1184 and CVE-2011-2526.
  - Thank you to Marc Deslauriers.  (Closes: #648038)

[ Niels Thykier ]
* Added build-arch and build-indep targets in d/rules.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
import java.security.MessageDigest;
24
24
import java.security.NoSuchAlgorithmException;
25
25
import java.security.Principal;
 
26
import java.util.LinkedHashMap;
 
27
import java.util.Map;
26
28
import java.util.StringTokenizer;
27
29
 
28
30
import javax.servlet.http.HttpServletResponse;
29
31
 
30
32
 
 
33
import org.apache.catalina.LifecycleException;
31
34
import org.apache.catalina.Realm;
32
35
import org.apache.catalina.connector.Request;
33
36
import org.apache.catalina.connector.Response;
47
50
 * @version $Id: DigestAuthenticator.java 939336 2010-04-29 15:00:41Z kkolinko $
48
51
 */
49
52
 
50
 
public class DigestAuthenticator
51
 
    extends AuthenticatorBase {
 
53
public class DigestAuthenticator extends AuthenticatorBase {
 
54
 
52
55
    private static Log log = LogFactory.getLog(DigestAuthenticator.class);
53
56
 
54
57
 
67
70
        "org.apache.catalina.authenticator.DigestAuthenticator/1.0";
68
71
 
69
72
 
 
73
    /**
 
74
     * Tomcat's DIGEST implementation only supports auth quality of protection.
 
75
     */
 
76
    protected static final String QOP = "auth";
 
77
 
70
78
    // ----------------------------------------------------------- Constructors
71
79
 
72
80
 
92
100
 
93
101
 
94
102
    /**
 
103
     * List of client nonce values currently being tracked
 
104
     */
 
105
    protected Map<String,NonceInfo> cnonces;
 
106
 
 
107
 
 
108
    /**
 
109
     * Maximum number of client nonces to keep in the cache. If not specified,
 
110
     * the default value of 1000 is used.
 
111
     */
 
112
    protected int cnonceCacheSize = 1000;
 
113
 
 
114
 
 
115
    /**
95
116
     * Private key.
96
117
     */
97
 
    protected String key = "Catalina";
98
 
 
 
118
    protected String key = null;
 
119
 
 
120
 
 
121
    /**
 
122
     * How long server nonces are valid for in milliseconds. Defaults to 5
 
123
     * minutes.
 
124
     */
 
125
    protected long nonceValidity = 5 * 60 * 1000;
 
126
 
 
127
 
 
128
    /**
 
129
     * Opaque string.
 
130
     */
 
131
    protected String opaque;
 
132
 
 
133
 
 
134
    /**
 
135
     * Should the URI be validated as required by RFC2617? Can be disabled in
 
136
     * reverse proxies where the proxy has modified the URI.
 
137
     */
 
138
    protected boolean validateUri = true;
99
139
 
100
140
    // ------------------------------------------------------------- Properties
101
141
 
102
 
 
103
142
    /**
104
143
     * Return descriptive information about this Valve implementation.
105
144
     */
 
145
    @Override
106
146
    public String getInfo() {
107
147
 
108
148
        return (info);
110
150
    }
111
151
 
112
152
 
 
153
    public int getCnonceCacheSize() {
 
154
        return cnonceCacheSize;
 
155
    }
 
156
 
 
157
 
 
158
    public void setCnonceCacheSize(int cnonceCacheSize) {
 
159
        this.cnonceCacheSize = cnonceCacheSize;
 
160
    }
 
161
 
 
162
 
 
163
    public String getKey() {
 
164
        return key;
 
165
    }
 
166
 
 
167
 
 
168
    public void setKey(String key) {
 
169
        this.key = key;
 
170
    }
 
171
 
 
172
 
 
173
    public long getNonceValidity() {
 
174
        return nonceValidity;
 
175
    }
 
176
 
 
177
 
 
178
    public void setNonceValidity(long nonceValidity) {
 
179
        this.nonceValidity = nonceValidity;
 
180
    }
 
181
 
 
182
 
 
183
    public String getOpaque() {
 
184
        return opaque;
 
185
    }
 
186
 
 
187
 
 
188
    public void setOpaque(String opaque) {
 
189
        this.opaque = opaque;
 
190
    }
 
191
 
 
192
 
 
193
    public boolean isValidateUri() {
 
194
        return validateUri;
 
195
    }
 
196
 
 
197
 
 
198
    public void setValidateUri(boolean validateUri) {
 
199
        this.validateUri = validateUri;
 
200
    }
 
201
 
 
202
 
113
203
    // --------------------------------------------------------- Public Methods
114
204
 
115
 
 
116
205
    /**
117
206
     * Authenticate the user making this request, based on the specified
118
207
     * login configuration.  Return <code>true</code> if any specified
126
215
     *
127
216
     * @exception IOException if an input/output error occurs
128
217
     */
 
218
    @Override
129
219
    public boolean authenticate(Request request,
130
220
                                Response response,
131
221
                                LoginConfig config)
172
262
 
173
263
        // Validate any credentials already included with this request
174
264
        String authorization = request.getHeader("authorization");
 
265
        DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
 
266
                getKey(), cnonces, isValidateUri());
175
267
        if (authorization != null) {
176
 
            principal = findPrincipal(request, authorization, context.getRealm());
 
268
            if (digestInfo.validate(request, authorization, config)) {
 
269
                principal = digestInfo.authenticate(context.getRealm());
 
270
            }
 
271
            
177
272
            if (principal != null) {
178
273
                String username = parseUsername(authorization);
179
274
                register(request, response, principal,
185
280
 
186
281
        // Send an "unauthorized" response and an appropriate challenge
187
282
 
188
 
        // Next, generate a nOnce token (that is a token which is supposed
 
283
        // Next, generate a nonce token (that is a token which is supposed
189
284
        // to be unique).
190
 
        String nOnce = generateNOnce(request);
 
285
        String nonce = generateNonce(request);
191
286
 
192
 
        setAuthenticateHeader(request, response, config, nOnce);
 
287
        setAuthenticateHeader(request, response, config, nonce,
 
288
                digestInfo.isNonceStale());
193
289
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
194
290
        //      hres.flushBuffer();
195
291
        return (false);
201
297
 
202
298
 
203
299
    /**
204
 
     * Parse the specified authorization credentials, and return the
205
 
     * associated Principal that these credentials authenticate (if any)
206
 
     * from the specified Realm.  If there is no such Principal, return
207
 
     * <code>null</code>.
208
 
     *
209
 
     * @param request HTTP servlet request
210
 
     * @param authorization Authorization credentials from this request
211
 
     * @param realm Realm used to authenticate Principals
212
 
     */
213
 
    protected static Principal findPrincipal(Request request,
214
 
                                             String authorization,
215
 
                                             Realm realm) {
216
 
 
217
 
        //System.out.println("Authorization token : " + authorization);
218
 
        // Validate the authorization credentials format
219
 
        if (authorization == null)
220
 
            return (null);
221
 
        if (!authorization.startsWith("Digest "))
222
 
            return (null);
223
 
        authorization = authorization.substring(7).trim();
224
 
 
225
 
        // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
226
 
        String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
227
 
 
228
 
        String userName = null;
229
 
        String realmName = null;
230
 
        String nOnce = null;
231
 
        String nc = null;
232
 
        String cnonce = null;
233
 
        String qop = null;
234
 
        String uri = null;
235
 
        String response = null;
236
 
        String method = request.getMethod();
237
 
 
238
 
        for (int i = 0; i < tokens.length; i++) {
239
 
            String currentToken = tokens[i];
240
 
            if (currentToken.length() == 0)
241
 
                continue;
242
 
 
243
 
            int equalSign = currentToken.indexOf('=');
244
 
            if (equalSign < 0)
245
 
                return null;
246
 
            String currentTokenName =
247
 
                currentToken.substring(0, equalSign).trim();
248
 
            String currentTokenValue =
249
 
                currentToken.substring(equalSign + 1).trim();
250
 
            if ("username".equals(currentTokenName))
251
 
                userName = removeQuotes(currentTokenValue);
252
 
            if ("realm".equals(currentTokenName))
253
 
                realmName = removeQuotes(currentTokenValue, true);
254
 
            if ("nonce".equals(currentTokenName))
255
 
                nOnce = removeQuotes(currentTokenValue);
256
 
            if ("nc".equals(currentTokenName))
257
 
                nc = removeQuotes(currentTokenValue);
258
 
            if ("cnonce".equals(currentTokenName))
259
 
                cnonce = removeQuotes(currentTokenValue);
260
 
            if ("qop".equals(currentTokenName))
261
 
                qop = removeQuotes(currentTokenValue);
262
 
            if ("uri".equals(currentTokenName))
263
 
                uri = removeQuotes(currentTokenValue);
264
 
            if ("response".equals(currentTokenName))
265
 
                response = removeQuotes(currentTokenValue);
266
 
        }
267
 
 
268
 
        if ( (userName == null) || (realmName == null) || (nOnce == null)
269
 
             || (uri == null) || (response == null) )
270
 
            return null;
271
 
 
272
 
        // Second MD5 digest used to calculate the digest :
273
 
        // MD5(Method + ":" + uri)
274
 
        String a2 = method + ":" + uri;
275
 
        //System.out.println("A2:" + a2);
276
 
 
277
 
        byte[] buffer = null;
278
 
        synchronized (md5Helper) {
279
 
            buffer = md5Helper.digest(a2.getBytes());
280
 
        }
281
 
        String md5a2 = md5Encoder.encode(buffer);
282
 
 
283
 
        return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop,
284
 
                                   realmName, md5a2));
285
 
 
286
 
    }
287
 
 
288
 
 
289
 
    /**
290
300
     * Parse the username from the specified authorization string.  If none
291
301
     * can be identified, return <code>null</code>
292
302
     *
294
304
     */
295
305
    protected String parseUsername(String authorization) {
296
306
 
297
 
        //System.out.println("Authorization token : " + authorization);
298
307
        // Validate the authorization credentials format
299
308
        if (authorization == null)
300
309
            return (null);
354
363
     *
355
364
     * @param request HTTP Servlet request
356
365
     */
357
 
    protected String generateNOnce(Request request) {
 
366
    protected String generateNonce(Request request) {
358
367
 
359
368
        long currentTime = System.currentTimeMillis();
360
369
 
361
 
        String nOnceValue = request.getRemoteAddr() + ":" +
362
 
            currentTime + ":" + key;
 
370
        
 
371
        String ipTimeKey =
 
372
            request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
363
373
 
364
 
        byte[] buffer = null;
 
374
        byte[] buffer;
365
375
        synchronized (md5Helper) {
366
 
            buffer = md5Helper.digest(nOnceValue.getBytes());
 
376
            buffer = md5Helper.digest(ipTimeKey.getBytes());
367
377
        }
368
 
        nOnceValue = md5Encoder.encode(buffer);
369
378
 
370
 
        return nOnceValue;
 
379
        return currentTime + ":" + md5Encoder.encode(buffer);
371
380
    }
372
381
 
373
382
 
379
388
     *      WWW-Authenticate    = "WWW-Authenticate" ":" "Digest"
380
389
     *                            digest-challenge
381
390
     *
382
 
     *      digest-challenge    = 1#( realm | [ domain ] | nOnce |
 
391
     *      digest-challenge    = 1#( realm | [ domain ] | nonce |
383
392
     *                  [ digest-opaque ] |[ stale ] | [ algorithm ] )
384
393
     *
385
394
     *      realm               = "realm" "=" realm-value
396
405
     * @param response HTTP Servlet response
397
406
     * @param config    Login configuration describing how authentication
398
407
     *              should be performed
399
 
     * @param nOnce nonce token
 
408
     * @param nonce nonce token
400
409
     */
401
410
    protected void setAuthenticateHeader(Request request,
402
411
                                         Response response,
403
412
                                         LoginConfig config,
404
 
                                         String nOnce) {
 
413
                                         String nonce,
 
414
                                         boolean isNonceStale) {
405
415
 
406
416
        // Get the realm name
407
417
        String realmName = config.getRealmName();
408
418
        if (realmName == null)
409
419
            realmName = REALM_NAME;
410
420
 
411
 
        byte[] buffer = null;
412
 
        synchronized (md5Helper) {
413
 
            buffer = md5Helper.digest(nOnce.getBytes());
 
421
        String authenticateHeader;
 
422
        if (isNonceStale) {
 
423
            authenticateHeader = "Digest realm=\"" + realmName + "\", " +
 
424
            "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
 
425
            getOpaque() + "\", stale=true";
 
426
        } else {
 
427
            authenticateHeader = "Digest realm=\"" + realmName + "\", " +
 
428
            "qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
 
429
            getOpaque() + "\"";
414
430
        }
415
431
 
416
 
        String authenticateHeader = "Digest realm=\"" + realmName + "\", "
417
 
            +  "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\""
418
 
            + md5Encoder.encode(buffer) + "\"";
419
432
        response.setHeader("WWW-Authenticate", authenticateHeader);
420
433
 
421
434
    }
422
435
 
423
436
 
 
437
    // ------------------------------------------------------- Lifecycle Methods
 
438
    
 
439
    @Override
 
440
    public void start() throws LifecycleException {
 
441
        super.start();
 
442
        
 
443
        // Generate a random secret key
 
444
        if (getKey() == null) {
 
445
            setKey(generateSessionId());
 
446
        }
 
447
        
 
448
        // Generate the opaque string the same way
 
449
        if (getOpaque() == null) {
 
450
            setOpaque(generateSessionId());
 
451
        }
 
452
        
 
453
        cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
 
454
 
 
455
            private static final long serialVersionUID = 1L;
 
456
            private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
 
457
 
 
458
            private long lastLog = 0;
 
459
 
 
460
            @Override
 
461
            protected boolean removeEldestEntry(
 
462
                    Map.Entry<String,NonceInfo> eldest) {
 
463
                // This is called from a sync so keep it simple
 
464
                long currentTime = System.currentTimeMillis();
 
465
                if (size() > getCnonceCacheSize()) {
 
466
                    if (lastLog < currentTime &&
 
467
                            currentTime - eldest.getValue().getTimestamp() <
 
468
                            getNonceValidity()) {
 
469
                        // Replay attack is possible
 
470
                        log.warn(sm.getString(
 
471
                                "digestAuthenticator.cacheRemove"));
 
472
                        lastLog = currentTime + LOG_SUPPRESS_TIME;
 
473
                    }
 
474
                    return true;
 
475
                }
 
476
                return false;
 
477
            }
 
478
        };
 
479
    }
 
480
 
 
481
    private static class DigestInfo {
 
482
 
 
483
        private String opaque;
 
484
        private long nonceValidity;
 
485
        private String key;
 
486
        private Map<String,NonceInfo> cnonces;
 
487
        private boolean validateUri = true;
 
488
 
 
489
        private String userName = null;
 
490
        private String method = null;
 
491
        private String uri = null;
 
492
        private String response = null;
 
493
        private String nonce = null;
 
494
        private String nc = null;
 
495
        private String cnonce = null;
 
496
        private String realmName = null;
 
497
        private String qop = null;
 
498
 
 
499
        private boolean nonceStale = false;
 
500
 
 
501
 
 
502
        public DigestInfo(String opaque, long nonceValidity, String key,
 
503
                Map<String,NonceInfo> cnonces, boolean validateUri) {
 
504
            this.opaque = opaque;
 
505
            this.nonceValidity = nonceValidity;
 
506
            this.key = key;
 
507
            this.cnonces = cnonces;
 
508
            this.validateUri = validateUri;
 
509
        }
 
510
 
 
511
        public boolean validate(Request request, String authorization,
 
512
                LoginConfig config) {
 
513
            // Validate the authorization credentials format
 
514
            if (authorization == null) {
 
515
                return false;
 
516
            }
 
517
            if (!authorization.startsWith("Digest ")) {
 
518
                return false;
 
519
            }
 
520
            authorization = authorization.substring(7).trim();
 
521
 
 
522
            // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
 
523
            String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
 
524
 
 
525
            method = request.getMethod();
 
526
            String opaque = null;
 
527
 
 
528
            for (int i = 0; i < tokens.length; i++) {
 
529
                String currentToken = tokens[i];
 
530
                if (currentToken.length() == 0)
 
531
                    continue;
 
532
 
 
533
                int equalSign = currentToken.indexOf('=');
 
534
                if (equalSign < 0) {
 
535
                    return false;
 
536
                }
 
537
                String currentTokenName =
 
538
                    currentToken.substring(0, equalSign).trim();
 
539
                String currentTokenValue =
 
540
                    currentToken.substring(equalSign + 1).trim();
 
541
                if ("username".equals(currentTokenName))
 
542
                    userName = removeQuotes(currentTokenValue);
 
543
                if ("realm".equals(currentTokenName))
 
544
                    realmName = removeQuotes(currentTokenValue, true);
 
545
                if ("nonce".equals(currentTokenName))
 
546
                    nonce = removeQuotes(currentTokenValue);
 
547
                if ("nc".equals(currentTokenName))
 
548
                    nc = removeQuotes(currentTokenValue);
 
549
                if ("cnonce".equals(currentTokenName))
 
550
                    cnonce = removeQuotes(currentTokenValue);
 
551
                if ("qop".equals(currentTokenName))
 
552
                    qop = removeQuotes(currentTokenValue);
 
553
                if ("uri".equals(currentTokenName))
 
554
                    uri = removeQuotes(currentTokenValue);
 
555
                if ("response".equals(currentTokenName))
 
556
                    response = removeQuotes(currentTokenValue);
 
557
                if ("opaque".equals(currentTokenName))
 
558
                    opaque = removeQuotes(currentTokenValue);
 
559
            }
 
560
 
 
561
            if ( (userName == null) || (realmName == null) || (nonce == null)
 
562
                 || (uri == null) || (response == null) ) {
 
563
                return false;
 
564
            }
 
565
 
 
566
            // Validate the URI - should match the request line sent by client
 
567
            if (validateUri) {
 
568
                String uriQuery;
 
569
                String query = request.getQueryString();
 
570
                if (query == null) {
 
571
                    uriQuery = request.getRequestURI();
 
572
                } else {
 
573
                    uriQuery = request.getRequestURI() + "?" + query;
 
574
                }
 
575
                if (!uri.equals(uriQuery)) {
 
576
                    return false;
 
577
                }
 
578
            }
 
579
 
 
580
            // Validate the Realm name
 
581
            String lcRealm = config.getRealmName();
 
582
            if (lcRealm == null) {
 
583
                lcRealm = REALM_NAME;
 
584
            }
 
585
            if (!lcRealm.equals(realmName)) {
 
586
                return false;
 
587
            }
 
588
            
 
589
            // Validate the opaque string
 
590
            if (!this.opaque.equals(opaque)) {
 
591
                return false;
 
592
            }
 
593
 
 
594
            // Validate nonce
 
595
            int i = nonce.indexOf(":");
 
596
            if (i < 0 || (i + 1) == nonce.length()) {
 
597
                return false;
 
598
            }
 
599
            long nonceTime;
 
600
            try {
 
601
                nonceTime = Long.parseLong(nonce.substring(0, i));
 
602
            } catch (NumberFormatException nfe) {
 
603
                return false;
 
604
            }
 
605
            String md5clientIpTimeKey = nonce.substring(i + 1);
 
606
            long currentTime = System.currentTimeMillis();
 
607
            if ((currentTime - nonceTime) > nonceValidity) {
 
608
                nonceStale = true;
 
609
                return false;
 
610
            }
 
611
            String serverIpTimeKey =
 
612
                request.getRemoteAddr() + ":" + nonceTime + ":" + key;
 
613
            byte[] buffer = null;
 
614
            synchronized (md5Helper) {
 
615
                buffer = md5Helper.digest(serverIpTimeKey.getBytes());
 
616
            }
 
617
            String md5ServerIpTimeKey = md5Encoder.encode(buffer);
 
618
            if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
 
619
                return false;
 
620
            }
 
621
 
 
622
            // Validate qop
 
623
            if (qop != null && !QOP.equals(qop)) {
 
624
                return false;
 
625
            }
 
626
 
 
627
            // Validate cnonce and nc
 
628
            // Check if presence of nc and nonce is consistent with presence of qop
 
629
            if (qop == null) {
 
630
                if (cnonce != null || nc != null) {
 
631
                    return false;
 
632
                }
 
633
            } else {
 
634
                if (cnonce == null || nc == null) {
 
635
                    return false;
 
636
                }
 
637
                if (nc.length() != 8) {
 
638
                    return false;
 
639
                }
 
640
                long count;
 
641
                try {
 
642
                    count = Long.parseLong(nc, 16);
 
643
                } catch (NumberFormatException nfe) {
 
644
                    return false;
 
645
                }
 
646
                NonceInfo info;
 
647
                synchronized (cnonces) {
 
648
                    info = cnonces.get(cnonce);
 
649
                }
 
650
                if (info == null) {
 
651
                    info = new NonceInfo();
 
652
                } else {
 
653
                    if (count <= info.getCount()) {
 
654
                        return false;
 
655
                    }
 
656
                }
 
657
                info.setCount(count);
 
658
                info.setTimestamp(currentTime);
 
659
                synchronized (cnonces) {
 
660
                    cnonces.put(cnonce, info);
 
661
                }
 
662
            }
 
663
            return true;
 
664
        }
 
665
 
 
666
        public boolean isNonceStale() {
 
667
            return nonceStale;
 
668
        }
 
669
 
 
670
        public Principal authenticate(Realm realm) {
 
671
            // Second MD5 digest used to calculate the digest :
 
672
            // MD5(Method + ":" + uri)
 
673
            String a2 = method + ":" + uri;
 
674
 
 
675
            byte[] buffer;
 
676
            synchronized (md5Helper) {
 
677
                buffer = md5Helper.digest(a2.getBytes());
 
678
            }
 
679
            String md5a2 = md5Encoder.encode(buffer);
 
680
 
 
681
            return realm.authenticate(userName, response, nonce, nc, cnonce,
 
682
                    qop, realmName, md5a2);
 
683
        }
 
684
 
 
685
    }
 
686
 
 
687
    private static class NonceInfo {
 
688
        private volatile long count;
 
689
        private volatile long timestamp;
 
690
        
 
691
        public void setCount(long l) {
 
692
            count = l;
 
693
        }
 
694
        
 
695
        public long getCount() {
 
696
            return count;
 
697
        }
 
698
        
 
699
        public void setTimestamp(long l) {
 
700
            timestamp = l;
 
701
        }
 
702
        
 
703
        public long getTimestamp() {
 
704
            return timestamp;
 
705
        }
 
706
    }
424
707
}