~ubuntu-branches/ubuntu/maverick/tomcat6/maverick

« back to all changes in this revision

Viewing changes to .pc/0012-Prevent-disclosure-of-host-name-or-IP-address.patch/java/org/apache/catalina/authenticator/DigestAuthenticator.java

  • Committer: Bazaar Package Importer
  • Author(s): Torsten Werner
  • Date: 2010-06-28 21:41:31 UTC
  • mfrom: (2.2.15 sid)
  • Revision ID: james.westby@ubuntu.com-20100628214131-3wktukpb3lgdf83h
Tags: 6.0.26-5
* Convert patches to dep3 format.
* Backport security fix from trunk to fix CVE-2010-1157. (Closes: #587447)
* Set urgency to medium due to the security fix.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
 
3
 * contributor license agreements.  See the NOTICE file distributed with
 
4
 * this work for additional information regarding copyright ownership.
 
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
 
6
 * (the "License"); you may not use this file except in compliance with
 
7
 * the License.  You may obtain a copy of the License at
 
8
 * 
 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
 
10
 * 
 
11
 * Unless required by applicable law or agreed to in writing, software
 
12
 * distributed under the License is distributed on an "AS IS" BASIS,
 
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
14
 * See the License for the specific language governing permissions and
 
15
 * limitations under the License.
 
16
 */
 
17
 
 
18
 
 
19
package org.apache.catalina.authenticator;
 
20
 
 
21
 
 
22
import java.io.IOException;
 
23
import java.security.MessageDigest;
 
24
import java.security.NoSuchAlgorithmException;
 
25
import java.security.Principal;
 
26
import java.util.StringTokenizer;
 
27
 
 
28
import javax.servlet.http.HttpServletResponse;
 
29
 
 
30
 
 
31
import org.apache.catalina.Realm;
 
32
import org.apache.catalina.connector.Request;
 
33
import org.apache.catalina.connector.Response;
 
34
import org.apache.catalina.deploy.LoginConfig;
 
35
import org.apache.catalina.util.MD5Encoder;
 
36
import org.apache.juli.logging.Log;
 
37
import org.apache.juli.logging.LogFactory;
 
38
 
 
39
 
 
40
 
 
41
/**
 
42
 * An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
 
43
 * Authentication (see RFC 2069).
 
44
 *
 
45
 * @author Craig R. McClanahan
 
46
 * @author Remy Maucherat
 
47
 * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (Di, 24. Okt 2006) $
 
48
 */
 
49
 
 
50
public class DigestAuthenticator
 
51
    extends AuthenticatorBase {
 
52
    private static Log log = LogFactory.getLog(DigestAuthenticator.class);
 
53
 
 
54
 
 
55
    // -------------------------------------------------------------- Constants
 
56
 
 
57
    /**
 
58
     * The MD5 helper object for this class.
 
59
     */
 
60
    protected static final MD5Encoder md5Encoder = new MD5Encoder();
 
61
 
 
62
 
 
63
    /**
 
64
     * Descriptive information about this implementation.
 
65
     */
 
66
    protected static final String info =
 
67
        "org.apache.catalina.authenticator.DigestAuthenticator/1.0";
 
68
 
 
69
 
 
70
    // ----------------------------------------------------------- Constructors
 
71
 
 
72
 
 
73
    public DigestAuthenticator() {
 
74
        super();
 
75
        try {
 
76
            if (md5Helper == null)
 
77
                md5Helper = MessageDigest.getInstance("MD5");
 
78
        } catch (NoSuchAlgorithmException e) {
 
79
            e.printStackTrace();
 
80
            throw new IllegalStateException();
 
81
        }
 
82
    }
 
83
 
 
84
 
 
85
    // ----------------------------------------------------- Instance Variables
 
86
 
 
87
 
 
88
    /**
 
89
     * MD5 message digest provider.
 
90
     */
 
91
    protected static MessageDigest md5Helper;
 
92
 
 
93
 
 
94
    /**
 
95
     * Private key.
 
96
     */
 
97
    protected String key = "Catalina";
 
98
 
 
99
 
 
100
    // ------------------------------------------------------------- Properties
 
101
 
 
102
 
 
103
    /**
 
104
     * Return descriptive information about this Valve implementation.
 
105
     */
 
106
    public String getInfo() {
 
107
 
 
108
        return (info);
 
109
 
 
110
    }
 
111
 
 
112
 
 
113
    // --------------------------------------------------------- Public Methods
 
114
 
 
115
 
 
116
    /**
 
117
     * Authenticate the user making this request, based on the specified
 
118
     * login configuration.  Return <code>true</code> if any specified
 
119
     * constraint has been satisfied, or <code>false</code> if we have
 
120
     * created a response challenge already.
 
121
     *
 
122
     * @param request Request we are processing
 
123
     * @param response Response we are creating
 
124
     * @param config    Login configuration describing how authentication
 
125
     *              should be performed
 
126
     *
 
127
     * @exception IOException if an input/output error occurs
 
128
     */
 
129
    public boolean authenticate(Request request,
 
130
                                Response response,
 
131
                                LoginConfig config)
 
132
        throws IOException {
 
133
 
 
134
        // Have we already authenticated someone?
 
135
        Principal principal = request.getUserPrincipal();
 
136
        //String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
 
137
        if (principal != null) {
 
138
            if (log.isDebugEnabled())
 
139
                log.debug("Already authenticated '" + principal.getName() + "'");
 
140
            // Associate the session with any existing SSO session in order
 
141
            // to get coordinated session invalidation at logout
 
142
            String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
 
143
            if (ssoId != null)
 
144
                associate(ssoId, request.getSessionInternal(true));
 
145
            return (true);
 
146
        }
 
147
 
 
148
        // NOTE: We don't try to reauthenticate using any existing SSO session,
 
149
        // because that will only work if the original authentication was
 
150
        // BASIC or FORM, which are less secure than the DIGEST auth-type
 
151
        // specified for this webapp
 
152
        //
 
153
        // Uncomment below to allow previous FORM or BASIC authentications
 
154
        // to authenticate users for this webapp
 
155
        // TODO make this a configurable attribute (in SingleSignOn??)
 
156
        /*
 
157
        // Is there an SSO session against which we can try to reauthenticate?
 
158
        if (ssoId != null) {
 
159
            if (log.isDebugEnabled())
 
160
                log.debug("SSO Id " + ssoId + " set; attempting " +
 
161
                          "reauthentication");
 
162
            // Try to reauthenticate using data cached by SSO.  If this fails,
 
163
            // either the original SSO logon was of DIGEST or SSL (which
 
164
            // we can't reauthenticate ourselves because there is no
 
165
            // cached username and password), or the realm denied
 
166
            // the user's reauthentication for some reason.
 
167
            // In either case we have to prompt the user for a logon
 
168
            if (reauthenticateFromSSO(ssoId, request))
 
169
                return true;
 
170
        }
 
171
        */
 
172
 
 
173
        // Validate any credentials already included with this request
 
174
        String authorization = request.getHeader("authorization");
 
175
        if (authorization != null) {
 
176
            principal = findPrincipal(request, authorization, context.getRealm());
 
177
            if (principal != null) {
 
178
                String username = parseUsername(authorization);
 
179
                register(request, response, principal,
 
180
                         Constants.DIGEST_METHOD,
 
181
                         username, null);
 
182
                return (true);
 
183
            }
 
184
        }
 
185
 
 
186
        // Send an "unauthorized" response and an appropriate challenge
 
187
 
 
188
        // Next, generate a nOnce token (that is a token which is supposed
 
189
        // to be unique).
 
190
        String nOnce = generateNOnce(request);
 
191
 
 
192
        setAuthenticateHeader(request, response, config, nOnce);
 
193
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
 
194
        //      hres.flushBuffer();
 
195
        return (false);
 
196
 
 
197
    }
 
198
 
 
199
 
 
200
    // ------------------------------------------------------ Protected Methods
 
201
 
 
202
 
 
203
    /**
 
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
     * Parse the username from the specified authorization string.  If none
 
291
     * can be identified, return <code>null</code>
 
292
     *
 
293
     * @param authorization Authorization string to be parsed
 
294
     */
 
295
    protected String parseUsername(String authorization) {
 
296
 
 
297
        //System.out.println("Authorization token : " + authorization);
 
298
        // Validate the authorization credentials format
 
299
        if (authorization == null)
 
300
            return (null);
 
301
        if (!authorization.startsWith("Digest "))
 
302
            return (null);
 
303
        authorization = authorization.substring(7).trim();
 
304
 
 
305
        StringTokenizer commaTokenizer =
 
306
            new StringTokenizer(authorization, ",");
 
307
 
 
308
        while (commaTokenizer.hasMoreTokens()) {
 
309
            String currentToken = commaTokenizer.nextToken();
 
310
            int equalSign = currentToken.indexOf('=');
 
311
            if (equalSign < 0)
 
312
                return null;
 
313
            String currentTokenName =
 
314
                currentToken.substring(0, equalSign).trim();
 
315
            String currentTokenValue =
 
316
                currentToken.substring(equalSign + 1).trim();
 
317
            if ("username".equals(currentTokenName))
 
318
                return (removeQuotes(currentTokenValue));
 
319
        }
 
320
 
 
321
        return (null);
 
322
 
 
323
    }
 
324
 
 
325
 
 
326
    /**
 
327
     * Removes the quotes on a string. RFC2617 states quotes are optional for
 
328
     * all parameters except realm.
 
329
     */
 
330
    protected static String removeQuotes(String quotedString,
 
331
                                         boolean quotesRequired) {
 
332
        //support both quoted and non-quoted
 
333
        if (quotedString.length() > 0 && quotedString.charAt(0) != '"' &&
 
334
                !quotesRequired) {
 
335
            return quotedString;
 
336
        } else if (quotedString.length() > 2) {
 
337
            return quotedString.substring(1, quotedString.length() - 1);
 
338
        } else {
 
339
            return new String();
 
340
        }
 
341
    }
 
342
 
 
343
    /**
 
344
     * Removes the quotes on a string.
 
345
     */
 
346
    protected static String removeQuotes(String quotedString) {
 
347
        return removeQuotes(quotedString, false);
 
348
    }
 
349
 
 
350
    /**
 
351
     * Generate a unique token. The token is generated according to the
 
352
     * following pattern. NOnceToken = Base64 ( MD5 ( client-IP ":"
 
353
     * time-stamp ":" private-key ) ).
 
354
     *
 
355
     * @param request HTTP Servlet request
 
356
     */
 
357
    protected String generateNOnce(Request request) {
 
358
 
 
359
        long currentTime = System.currentTimeMillis();
 
360
 
 
361
        String nOnceValue = request.getRemoteAddr() + ":" +
 
362
            currentTime + ":" + key;
 
363
 
 
364
        byte[] buffer = null;
 
365
        synchronized (md5Helper) {
 
366
            buffer = md5Helper.digest(nOnceValue.getBytes());
 
367
        }
 
368
        nOnceValue = md5Encoder.encode(buffer);
 
369
 
 
370
        return nOnceValue;
 
371
    }
 
372
 
 
373
 
 
374
    /**
 
375
     * Generates the WWW-Authenticate header.
 
376
     * <p>
 
377
     * The header MUST follow this template :
 
378
     * <pre>
 
379
     *      WWW-Authenticate    = "WWW-Authenticate" ":" "Digest"
 
380
     *                            digest-challenge
 
381
     *
 
382
     *      digest-challenge    = 1#( realm | [ domain ] | nOnce |
 
383
     *                  [ digest-opaque ] |[ stale ] | [ algorithm ] )
 
384
     *
 
385
     *      realm               = "realm" "=" realm-value
 
386
     *      realm-value         = quoted-string
 
387
     *      domain              = "domain" "=" <"> 1#URI <">
 
388
     *      nonce               = "nonce" "=" nonce-value
 
389
     *      nonce-value         = quoted-string
 
390
     *      opaque              = "opaque" "=" quoted-string
 
391
     *      stale               = "stale" "=" ( "true" | "false" )
 
392
     *      algorithm           = "algorithm" "=" ( "MD5" | token )
 
393
     * </pre>
 
394
     *
 
395
     * @param request HTTP Servlet request
 
396
     * @param response HTTP Servlet response
 
397
     * @param config    Login configuration describing how authentication
 
398
     *              should be performed
 
399
     * @param nOnce nonce token
 
400
     */
 
401
    protected void setAuthenticateHeader(Request request,
 
402
                                         Response response,
 
403
                                         LoginConfig config,
 
404
                                         String nOnce) {
 
405
 
 
406
        // Get the realm name
 
407
        String realmName = config.getRealmName();
 
408
        if (realmName == null)
 
409
            realmName = request.getServerName() + ":"
 
410
                + request.getServerPort();
 
411
 
 
412
        byte[] buffer = null;
 
413
        synchronized (md5Helper) {
 
414
            buffer = md5Helper.digest(nOnce.getBytes());
 
415
        }
 
416
 
 
417
        String authenticateHeader = "Digest realm=\"" + realmName + "\", "
 
418
            +  "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\""
 
419
            + md5Encoder.encode(buffer) + "\"";
 
420
        response.setHeader("WWW-Authenticate", authenticateHeader);
 
421
 
 
422
    }
 
423
 
 
424
 
 
425
}