~ubuntu-branches/ubuntu/oneiric/tomcat6/oneiric

« 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): Thierry Carrez
  • Date: 2010-07-20 14:36:48 UTC
  • mfrom: (2.2.17 sid)
  • Revision ID: james.westby@ubuntu.com-20100720143648-23y81x6cq1kv1z00
Tags: 6.0.28-2
* Add debconf questions for user, group and Java options.
* Use ucf to install /etc/default/tomcat6 from a template
* Drop CATALINA_BASE and CATALINA_HOME from /etc/default/tomcat6 since we
  shouldn't encourage users to change those anyway

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
 
}