001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone.auth;
008:
009: import java.io.IOException;
010: import java.io.UnsupportedEncodingException;
011: import java.security.MessageDigest;
012: import java.security.NoSuchAlgorithmException;
013: import java.util.List;
014: import java.util.Random;
015: import java.util.Set;
016: import java.util.StringTokenizer;
017:
018: import javax.servlet.http.HttpServletRequest;
019: import javax.servlet.http.HttpServletRequestWrapper;
020: import javax.servlet.http.HttpServletResponse;
021:
022: import org.w3c.dom.Node;
023:
024: import winstone.AuthenticationPrincipal;
025: import winstone.AuthenticationRealm;
026: import winstone.Logger;
027: import winstone.WinstoneRequest;
028: import winstone.WinstoneResourceBundle;
029:
030: /**
031: * Implements the MD5 digest version of authentication
032: *
033: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
034: * @version $Id: DigestAuthenticationHandler.java,v 1.3 2004/05/22 06:53:45
035: * rickknowles Exp $
036: */
037: public class DigestAuthenticationHandler extends
038: BaseAuthenticationHandler {
039: private MessageDigest md5Digester;
040:
041: public DigestAuthenticationHandler(Node loginConfigNode,
042: List constraintNodes, Set rolesAllowed,
043: AuthenticationRealm realm) throws NoSuchAlgorithmException {
044: super (loginConfigNode, constraintNodes, rolesAllowed, realm);
045: this .md5Digester = MessageDigest.getInstance("MD5");
046: Logger.log(Logger.DEBUG, AUTH_RESOURCES,
047: "DigestAuthenticationHandler.Initialised", realmName);
048: }
049:
050: /**
051: * Call this once we know that we need to authenticate
052: */
053: protected void requestAuthentication(HttpServletRequest request,
054: HttpServletResponse response, String pathRequested)
055: throws IOException {
056: // Generate the one time token
057: String oneTimeToken = "WinstoneToken:"
058: + (new Random().nextDouble() * System
059: .currentTimeMillis());
060:
061: // Need to write the www-authenticate header
062: String authHeader = "Digest realm=\"" + this .realmName
063: + "\", qop=\"auth\", " + "nonce=\"" + oneTimeToken
064: + "\", opaque=\"" + md5Encode(oneTimeToken) + "\"";
065: response.setHeader("WWW-Authenticate", authHeader);
066:
067: // Return unauthorized
068: response
069: .sendError(
070: HttpServletResponse.SC_UNAUTHORIZED,
071: AUTH_RESOURCES
072: .getString("DigestAuthenticationHandler.UnauthorizedMessage"));
073: }
074:
075: /**
076: * Handling the (possible) response
077: *
078: * @return True if the request should continue, or false if we have
079: * intercepted it
080: */
081: protected boolean validatePossibleAuthenticationResponse(
082: HttpServletRequest request, HttpServletResponse response,
083: String pathRequested) throws IOException {
084: String authorization = request.getHeader("Authorization");
085: if (authorization == null)
086: return true;
087:
088: // Logger.log(Logger.FULL_DEBUG, "Authorization: " + authorization);
089: if (!authorization.startsWith("Digest"))
090: return true;
091:
092: // Extract tokens from auth string
093: String userName = null;
094: String realm = null;
095: String qop = null;
096: String algorithm = null;
097: String uri = null;
098: String nOnce = null;
099: String nc = null;
100: String cnOnce = null;
101: String clientResponseDigest = null;
102:
103: StringTokenizer st = new StringTokenizer(authorization
104: .substring(6).trim(), ",");
105: while (st.hasMoreTokens()) {
106: String token = st.nextToken().trim();
107: int equalPos = token.indexOf('=');
108: String paramName = token.substring(0, equalPos);
109: if (paramName.equals("username"))
110: userName = WinstoneResourceBundle.globalReplace(token
111: .substring(equalPos + 1).trim(), "\"", "");
112: else if (paramName.equals("realm"))
113: realm = WinstoneResourceBundle.globalReplace(token
114: .substring(equalPos + 1).trim(), "\"", "");
115: else if (paramName.equals("qop"))
116: qop = WinstoneResourceBundle.globalReplace(token
117: .substring(equalPos + 1).trim(), "\"", "");
118: else if (paramName.equals("algorithm"))
119: algorithm = WinstoneResourceBundle.globalReplace(token
120: .substring(equalPos + 1).trim(), "\"", "");
121: else if (paramName.equals("uri"))
122: uri = WinstoneResourceBundle.globalReplace(token
123: .substring(equalPos + 1).trim(), "\"", "");
124: else if (paramName.equals("nonce"))
125: nOnce = WinstoneResourceBundle.globalReplace(token
126: .substring(equalPos + 1).trim(), "\"", "");
127: else if (paramName.equals("nc"))
128: nc = WinstoneResourceBundle.globalReplace(token
129: .substring(equalPos + 1).trim(), "\"", "");
130: else if (paramName.equals("cnonce"))
131: cnOnce = WinstoneResourceBundle.globalReplace(token
132: .substring(equalPos + 1).trim(), "\"", "");
133: else if (paramName.equals("response"))
134: clientResponseDigest = WinstoneResourceBundle
135: .globalReplace(token.substring(equalPos + 1)
136: .trim(), "\"", "");
137: }
138:
139: // Throw out bad attempts
140: if ((userName == null) || (realm == null) || (qop == null)
141: || (uri == null) || (nOnce == null) || (nc == null)
142: || (cnOnce == null) || (clientResponseDigest == null))
143: return true;
144: else if ((algorithm != null) && !algorithm.equals("MD5"))
145: return true;
146:
147: // Get a user matching the username
148: AuthenticationPrincipal principal = this .realm
149: .retrieveUser(userName);
150: if (principal == null)
151: return true;
152:
153: // Compute the 2 digests and compare
154: String userRealmPasswordDigest = md5Encode(userName + ":"
155: + realm + ":" + principal.getPassword());
156: String methodURIDigest = md5Encode(request.getMethod() + ":"
157: + uri);
158: String serverResponseDigest = md5Encode(userRealmPasswordDigest
159: + ":" + nOnce + ":" + nc + ":" + cnOnce + ":" + qop
160: + ":" + methodURIDigest);
161: if (serverResponseDigest.equals(clientResponseDigest)) {
162: principal.setAuthType(HttpServletRequest.DIGEST_AUTH);
163: if (request instanceof WinstoneRequest)
164: ((WinstoneRequest) request).setRemoteUser(principal);
165: else if (request instanceof HttpServletRequestWrapper) {
166: HttpServletRequestWrapper wrapper = (HttpServletRequestWrapper) request;
167: if (wrapper.getRequest() instanceof WinstoneRequest)
168: ((WinstoneRequest) wrapper.getRequest())
169: .setRemoteUser(principal);
170: else
171: Logger.log(Logger.WARNING, AUTH_RESOURCES,
172: "DigestAuthenticationHandler.CantSetUser",
173: wrapper.getRequest().getClass().getName());
174: } else
175: Logger.log(Logger.WARNING, AUTH_RESOURCES,
176: "DigestAuthenticationHandler.CantSetUser",
177: request.getClass().getName());
178: }
179: return true;
180: }
181:
182: /**
183: * Returns a hex encoded MD5 digested version of the input string
184: * @param input The string to encode
185: * @return MD5 digested, hex encoded version of the input
186: */
187: public String md5Encode(String input)
188: throws UnsupportedEncodingException {
189: // Digest
190: byte digestBytes[] = this .md5Digester.digest(input
191: .getBytes("8859_1"));
192:
193: // Write out in hex format
194: char outArray[] = new char[32];
195: for (int n = 0; n < digestBytes.length; n++) {
196: int hiNibble = (digestBytes[n] & 0xFF) >> 4;
197: int loNibble = (digestBytes[n] & 0xF);
198: outArray[2 * n] = (hiNibble > 9 ? (char) (hiNibble + 87)
199: : (char) (hiNibble + 48));
200: outArray[2 * n + 1] = (loNibble > 9 ? (char) (loNibble + 87)
201: : (char) (loNibble + 48));
202: }
203: return new String(outArray);
204: }
205: }
|