/* * eXist Open Source Native XML Database * Copyright (C) 2001-2008 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id$ */ package org.exist.http.servlets; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.exist.security.MessageDigester; import org.exist.security.SecurityManager; import org.exist.security.User; import org.exist.storage.BrokerPool; /** * An Authenticator that uses MD5 Digest Authentication. * * @author wolf */ public class DigestAuthenticator implements Authenticator { private BrokerPool pool; public DigestAuthenticator(BrokerPool pool) { this.pool = pool; } public User authenticate(HttpServletRequest request, HttpServletResponse response) throws IOException { String credentials = request.getHeader("Authorization"); if(credentials == null) { sendChallenge(request, response); return null; } Digest digest = new Digest(request.getMethod()); parseCredentials(digest, credentials); SecurityManager secman = pool.getSecurityManager(); User user = secman.getUser(digest.username); if(user == null) { //If user does not exist then send a challenge request again sendChallenge(request, response); return null; } if(!digest.check(user.getDigestPassword())) { //If password is incorrect then send a challenge request again sendChallenge(request, response); return null; } return user; } public void sendChallenge(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setHeader("WWW-Authenticate", "Digest realm=\"exist\", " + "nonce=\"" + createNonce(request) + "\", " + "domain=\"" + request.getContextPath() + "\", " + "opaque=\"" + MessageDigester.md5(Integer.toString(hashCode(), 27),false) + '"'); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } private String createNonce(HttpServletRequest request) { return MessageDigester.md5(request.getRemoteAddr() + ':' + Long.toString(System.currentTimeMillis()) + ':' + Integer.toString(hashCode()),false); } private static void parseCredentials(Digest digest, String credentials) { credentials = credentials.substring("Digest ".length()); StringBuffer current = new StringBuffer(); String name = null, value; boolean inQuotedString = false; for(int i = 0; i < credentials.length(); i++) { char ch = credentials.charAt(i); switch(ch) { case ' ': break; case '"': case '\'': if(inQuotedString) { value = current.toString(); current.setLength(0); inQuotedString = false; if("username".equalsIgnoreCase(name)) digest.username = value; else if("realm".equalsIgnoreCase(name)) digest.realm = value; else if("nonce".equalsIgnoreCase(name)) digest.nonce = value; else if("uri".equalsIgnoreCase(name)) digest.uri = value; else if("response".equalsIgnoreCase(name)) digest.response = value; } else { value = null; inQuotedString = true; } break; case ',': name = null; break; case '=': name = current.toString(); current.setLength(0); break; default: current.append(ch); break; } } } private static class Digest { String method=null; String username = null; String realm = null; String nonce = null; String uri = null; String response=null; public Digest(String method) { this.method = method; } public boolean check(String credentials) throws IOException { if(credentials == null) // no password set for the user: return true return true; try { MessageDigest md = MessageDigest.getInstance("MD5"); // calc A2 digest md.reset(); md.update(method.getBytes("ISO-8859-1")); md.update((byte)':'); md.update(uri.getBytes("ISO-8859-1")); byte[] ha2=md.digest(); // calc digest md.update(credentials.getBytes("ISO-8859-1")); md.update((byte)':'); md.update(nonce.getBytes("ISO-8859-1")); md.update((byte)':'); md.update(MessageDigester.byteArrayToHex(ha2).getBytes("ISO-8859-1")); byte[] digest=md.digest(); // check digest return (MessageDigester.byteArrayToHex(digest).equalsIgnoreCase(response)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MD5 not supported"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Encoding not supported"); } } } }