Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 package org.apache.catalina.authenticator;
 
 import java.util.Map;
 
 
An Authenticator and Valve implementation of HTTP DIGEST Authentication (see RFC 2069).

Author(s):
Craig R. McClanahan
Remy Maucherat
Version:
$Id: DigestAuthenticator.java 1377853 2012-08-27 20:53:56Z markt $
 
 public class DigestAuthenticator extends AuthenticatorBase {
 
     // -------------------------------------------------------------- Constants
 
    
Tomcat's DIGEST implementation only supports auth quality of protection.
 
     protected static final String QOP = "auth";
 
     // ----------------------------------------------------------- Constructors
 
 
     public DigestAuthenticator() {
         super();
         setCache(false);
         try {
             if ( == null) {
                  = MessageDigest.getInstance("MD5");
             }
         } catch (NoSuchAlgorithmException e) {
             throw new IllegalStateException(e);
         }
     }
 
 
     // ----------------------------------------------------- Instance Variables
 

    
MD5 message digest provider.

Deprecated:
Unused - will be removed in Tomcat 8.0.x onwards
 
     protected static MessageDigest md5Helper;


    
List of server nonce values currently being tracked
 
     protected Map<String,NonceInfononces;


    
Maximum number of server nonces to keep in the cache. If not specified, the default value of 1000 is used.
 
     protected int nonceCacheSize = 1000;


    
Private key.
    protected String key = null;


    
How long server nonces are valid for in milliseconds. Defaults to 5 minutes.
    protected long nonceValidity = 5 * 60 * 1000;


    
Opaque string.
    protected String opaque;


    
Should the URI be validated as required by RFC2617? Can be disabled in reverse proxies where the proxy has modified the URI.
    protected boolean validateUri = true;
    // ------------------------------------------------------------- Properties
    public int getNonceCacheSize() {
        return ;
    }
    public void setNonceCacheSize(int nonceCacheSize) {
        this. = nonceCacheSize;
    }
    public String getKey() {
        return ;
    }
    public void setKey(String key) {
        this. = key;
    }
    public long getNonceValidity() {
        return ;
    }
    public void setNonceValidity(long nonceValidity) {
        this. = nonceValidity;
    }
    public String getOpaque() {
        return ;
    }
    public void setOpaque(String opaque) {
        this. = opaque;
    }
    public boolean isValidateUri() {
        return ;
    }
    public void setValidateUri(boolean validateUri) {
        this. = validateUri;
    }
    // --------------------------------------------------------- Public Methods

    
Authenticate the user making this request, based on the specified login configuration. Return true if any specified constraint has been satisfied, or false if we have created a response challenge already.

Parameters:
request Request we are processing
response Response we are creating
Throws:
java.io.IOException if an input/output error occurs
    @Override
    public boolean authenticate(Request request,
                                HttpServletResponse response,
                                LoginConfig config)
            throws IOException {
        // Have we already authenticated someone?
        Principal principal = request.getUserPrincipal();
        //String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
        if (principal != null) {
            // Associate the session with any existing SSO session in order
            // to get coordinated session invalidation at logout
            String ssoId = (Stringrequest.getNote(.);
            if (ssoId != null) {
                associate(ssoIdrequest.getSessionInternal(true));
            }
            return (true);
        }
        // NOTE: We don't try to reauthenticate using any existing SSO session,
        // because that will only work if the original authentication was
        // BASIC or FORM, which are less secure than the DIGEST auth-type
        // specified for this webapp
        //
        // Uncomment below to allow previous FORM or BASIC authentications
        // to authenticate users for this webapp
        // TODO make this a configurable attribute (in SingleSignOn??)
        /*
        // Is there an SSO session against which we can try to reauthenticate?
        if (ssoId != null) {
            if (log.isDebugEnabled())
                log.debug("SSO Id " + ssoId + " set; attempting " +
                          "reauthentication");
            // Try to reauthenticate using data cached by SSO.  If this fails,
            // either the original SSO logon was of DIGEST or SSL (which
            // we can't reauthenticate ourselves because there is no
            // cached username and password), or the realm denied
            // the user's reauthentication for some reason.
            // In either case we have to prompt the user for a logon
            if (reauthenticateFromSSO(ssoId, request))
                return true;
        }
        */
        // Validate any credentials already included with this request
        String authorization = request.getHeader("authorization");
        DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
                getKey(), isValidateUri());
        if (authorization != null) {
            if (digestInfo.parse(requestauthorization)) {
                if (digestInfo.validate(requestconfig)) {
                    principal = digestInfo.authenticate(.getRealm());
                }
                if (principal != null && !digestInfo.isNonceStale()) {
                    register(requestresponseprincipal,
                            .,
                            digestInfo.getUsername(), null);
                    return true;
                }
            }
        }
        // Send an "unauthorized" response and an appropriate challenge
        // Next, generate a nonce token (that is a token which is supposed
        // to be unique).
        String nonce = generateNonce(request);
        setAuthenticateHeader(requestresponseconfignonce,
                principal != null && digestInfo.isNonceStale());
        return false;
    }
    // ------------------------------------------------------ Protected Methods


    
Removes the quotes on a string. RFC2617 states quotes are optional for all parameters except realm.
    protected static String removeQuotes(String quotedString,
                                         boolean quotesRequired) {
        //support both quoted and non-quoted
        if (quotedString.length() > 0 && quotedString.charAt(0) != '"' &&
                !quotesRequired) {
            return quotedString;
        } else if (quotedString.length() > 2) {
            return quotedString.substring(1, quotedString.length() - 1);
        } else {
            return "";
        }
    }

    
Removes the quotes on a string.
    protected static String removeQuotes(String quotedString) {
        return removeQuotes(quotedStringfalse);
    }

    
Generate a unique token. The token is generated according to the following pattern. NOnceToken = Base64 ( MD5 ( client-IP ":" time-stamp ":" private-key ) ).

Parameters:
request HTTP Servlet request
    protected String generateNonce(Request request) {
        long currentTime = System.currentTimeMillis();
        String ipTimeKey =
            request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
        byte[] buffer = ConcurrentMessageDigest.digestMD5(
                ipTimeKey.getBytes(.));
        String nonce = currentTime + ":" + MD5Encoder.encode(buffer);
        NonceInfo info = new NonceInfo(currentTime, 100);
        synchronized () {
            .put(nonceinfo);
        }
        return nonce;
    }


    
Generates the WWW-Authenticate header.

The header MUST follow this template :

      WWW-Authenticate    = "WWW-Authenticate" ":" "Digest"
                            digest-challenge

      digest-challenge    = 1#( realm | [ domain ] | nonce |
                  [ digest-opaque ] |[ stale ] | [ algorithm ] )

      realm               = "realm" "=" realm-value
      realm-value         = quoted-string
      domain              = "domain" "=" <"> 1#URI <">
      nonce               = "nonce" "=" nonce-value
      nonce-value         = quoted-string
      opaque              = "opaque" "=" quoted-string
      stale               = "stale" "=" ( "true" | "false" )
      algorithm           = "algorithm" "=" ( "MD5" | token )
 

Parameters:
request HTTP Servlet request
response HTTP Servlet response
nonce nonce token
    protected void setAuthenticateHeader(HttpServletRequest request,
                                         HttpServletResponse response,
                                         LoginConfig config,
                                         String nonce,
                                         boolean isNonceStale) {
        // Get the realm name
        String realmName = config.getRealmName();
        if (realmName == null)
            realmName = ;
        String authenticateHeader;
        if (isNonceStale) {
            authenticateHeader = "Digest realm=\"" + realmName + "\", " +
            "qop=\"" +  + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
            getOpaque() + "\", stale=true";
        } else {
            authenticateHeader = "Digest realm=\"" + realmName + "\", " +
            "qop=\"" +  + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
            getOpaque() + "\"";
        }
        response.setHeader(authenticateHeader);
    }
    // ------------------------------------------------------- Lifecycle Methods
    @Override
    public void start() throws LifecycleException {
        super.start();
        
        Container engine = ;
        while (engine != null) {
            if (engine instanceof Engine)
                break;
            engine = engine.getParent();
        }
        Random random = null;
        if (engine != null && engine instanceof Engine && ((Engineengine).getService() != null) {
            random = ((Engineengine).getService().getRandom();
        }
        if (random == null) {
            // Shouldn't happen
            random = new SecureRandom();
        }
        // Generate a random secret key
        if (getKey() == null) {
            setKey(generateSessionId(random));
        }
        // Generate the opaque string the same way
        if (getOpaque() == null) {
            setOpaque(generateSessionId(random));
        }
            private static final long serialVersionUID = 1L;
            private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
            private long lastLog = 0;
            @Override
            protected boolean removeEldestEntry(
                    Map.Entry<String,NonceInfoeldest) {
                // This is called from a sync so keep it simple
                long currentTime = System.currentTimeMillis();
                if (size() > getNonceCacheSize()) {
                    if ( < currentTime &&
                            currentTime - eldest.getValue().getTimestamp() <
                            getNonceValidity()) {
                        // Replay attack is possible
                        ..digestCacheRemove();
                         = currentTime + ;
                    }
                    return true;
                }
                return false;
            }
        };
    }
    private static class DigestInfo {
        private final String opaque;
        private final long nonceValidity;
        private final String key;
        private final Map<String,NonceInfononces;
        private boolean validateUri = true;
        private String userName = null;
        private String method = null;
        private String uri = null;
        private String response = null;
        private String nonce = null;
        private String nc = null;
        private String cnonce = null;
        private String realmName = null;
        private String qop = null;
        private String opaqueReceived = null;
        private boolean nonceStale = false;
        public DigestInfo(String opaquelong nonceValidityString key,
                Map<String,NonceInfononcesboolean validateUri) {
            this. = opaque;
            this. = nonceValidity;
            this. = key;
            this. = nonces;
            this. = validateUri;
        }
        public String getUsername() {
            return ;
        }
        public boolean parse(Request requestString authorization) {
            // Validate the authorization credentials format
            if (authorization == null) {
                return false;
            }
            if (!authorization.startsWith("Digest ")) {
                return false;
            }
            authorization = authorization.substring(7).trim();
            // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
            String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
             = request.getMethod();
            for (int i = 0; i < tokens.lengthi++) {
                String currentToken = tokens[i];
                if (currentToken.length() == 0) {
                    continue;
                }
                int equalSign = currentToken.indexOf('=');
                if (equalSign < 0) {
                    return false;
                }
                String currentTokenName =
                    currentToken.substring(0, equalSign).trim();
                String currentTokenValue =
                    currentToken.substring(equalSign + 1).trim();
                if ("username".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
                if ("realm".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValuetrue);
                }
                if ("nonce".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
                if ("nc".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
                if ("cnonce".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
                if ("qop".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
                if ("uri".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
                if ("response".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
                if ("opaque".equals(currentTokenName)) {
                     = removeQuotes(currentTokenValue);
                }
            }
            return true;
        }
        public boolean validate(Request requestLoginConfig config) {
            if ( ( == null) || ( == null) || ( == null)
                 || ( == null) || ( == null) ) {
                return false;
            }
            // Validate the URI - should match the request line sent by client
            if () {
                String uriQuery;
                String query = request.getQueryString();
                if (query == null) {
                    uriQuery = request.getRequestURI();
                } else {
                    uriQuery = request.getRequestURI() + "?" + query;
                }
                if (!.equals(uriQuery)) {
                    // Some clients (older Android) use an absolute URI for
                    // DIGEST but a relative URI in the request line.
                    // request. 2.3.5 < fixed Android version <= 4.0.3
                    String host = request.getHeader("host");
                    String scheme = request.getScheme();
                    if (host != null && !uriQuery.startsWith(scheme)) {
                        StringBuilder absolute = new StringBuilder();
                        absolute.append(scheme);
                        absolute.append("://");
                        absolute.append(host);
                        absolute.append(uriQuery);
                        if (!.equals(absolute.toString())) {
                            return false;
                        }
                    } else {
                        return false;
                    }
                }
            }
            // Validate the Realm name
            String lcRealm = config.getRealmName();
            if (lcRealm == null) {
                lcRealm = ;
            }
            if (!lcRealm.equals()) {
                return false;
            }
            // Validate the opaque string
            if (!.equals()) {
                return false;
            }
            // Validate nonce
            int i = .indexOf(":");
            if (i < 0 || (i + 1) == .length()) {
                return false;
            }
            long nonceTime;
            try {
                nonceTime = Long.parseLong(.substring(0, i));
            } catch (NumberFormatException nfe) {
                return false;
            }
            String md5clientIpTimeKey = .substring(i + 1);
            long currentTime = System.currentTimeMillis();
            if ((currentTime - nonceTime) > ) {
                 = true;
                synchronized () {
                    .remove();
                }
            }
            String serverIpTimeKey =
                request.getRemoteAddr() + ":" + nonceTime + ":" + ;
            byte[] buffer = ConcurrentMessageDigest.digestMD5(
                    serverIpTimeKey.getBytes(.));
            String md5ServerIpTimeKey = MD5Encoder.encode(buffer);
            if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
                return false;
            }
            // Validate qop
            if ( != null && !.equals()) {
                return false;
            }
            // Validate cnonce and nc
            // Check if presence of nc and Cnonce is consistent with presence of qop
            if ( == null) {
                if ( != null ||  != null) {
                    return false;
                }
            } else {
                if ( == null ||  == null) {
                    return false;
                }
                // RFC 2617 says nc must be 8 digits long. Older Android clients
                // use 6. 2.3.5 < fixed Android version <= 4.0.3
                if (.length() < 6 || .length() > 8) {
                    return false;
                }
                long count;
                try {
                    count = Long.parseLong(, 16);
                } catch (NumberFormatException nfe) {
                    return false;
                }
                NonceInfo info;
                synchronized () {
                    info = .get();
                }
                if (info == null) {
                    // Nonce is valid but not in cache. It must have dropped out
                    // of the cache - force a re-authentication
                     = true;
                } else {
                    if (!info.nonceCountValid(count)) {
                        return false;
                    }
                }
            }
            return true;
        }
        public boolean isNonceStale() {
            return ;
        }
        public Principal authenticate(Realm realm) {
            // Second MD5 digest used to calculate the digest :
            // MD5(Method + ":" + uri)
            String a2 =  + ":" + ;
            byte[] buffer = ConcurrentMessageDigest.digestMD5(
                    a2.getBytes(.));
            String md5a2 = MD5Encoder.encode(buffer);
            return realm.authenticate(,
                    md5a2);
        }
    }
    private static class NonceInfo {
        private volatile long timestamp;
        private volatile boolean seen[];
        private volatile int offset;
        private volatile int count = 0;
        public NonceInfo(long currentTimeint seenWindowSize) {
            this. = currentTime;
             = new boolean[seenWindowSize];
             = seenWindowSize / 2;
        }
        public synchronized boolean nonceCountValid(long nonceCount) {
            if (( - ) >= nonceCount ||
                    (nonceCount >  -  + .)) {
                return false;
            }
            int checkIndex = (int) ((nonceCount + ) % .);
            if ([checkIndex]) {
                return false;
            } else {
                [checkIndex] = true;
                [ % .] = false;
                ++;
                return true;
            }
        }
        public long getTimestamp() {
            return ;
        }
    }
New to GrepCode? Check out our FAQ X