/* * JOSSO: Java Open Single Sign-On * * Copyright 2004-2009, Atricore, Inc. * * This 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.1 of * the License, or (at your option) any later version. * * This software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.josso.auth.scheme; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.josso.auth.Credential; import org.josso.auth.CredentialProvider; import org.josso.auth.SimplePrincipal; import org.josso.auth.exceptions.SSOAuthenticationException; import org.josso.auth.util.Crypt; import org.josso.auth.util.CipherUtil; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; /** * Basic authentication scheme, supporting username and password credentials. * <p/> * Configuration properties supported by this authenticator are : * <ul> * <li>hashAlgorithm: The message digest algorithm to be used when hashing passwords. * If not specified, no hashing is used. * This must be an algorithm supported by the java.security.MessageDigest class on your platform. * For J2SE 1.4.2 you can check : * <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppB">Java Cryptography Architecture API Specification & Reference - Apendix B : Algorithms</a> * </li> * <li>hashEncoding: The econding used to store hashed passwords. * Supported values are HEX, BASE64.</li> * <li>ignorePasswordCase: If true, password case will be igonred. This property is ignored if a hashAlgorithm was specified. * Default to false.</li> * <li>ignoreUserCase: If ture, username case will be ignored.</li> * <li>credential-store: The credential store configured for this authenticator. * Check specific stores for specific configuraiton options</li> * <li>credential-store-key-adapter: The credential store key adapter configured for this authenticator. * Check specific stores for specific configuraiton options</li> * </ul> * </p> * <p/> * Sample authenticator configuration for basic authentication (username/password) : * </p> * <pre> * <authentication-scheme> * <p/> * <class>org.josso.auth.scheme.UsernamePasswordAuthScheme</class> * <hashAlgorithm>MD5</hashAlgorithm> * <hashEncoding>HEX</hashEncoding> * <ignorePasswordCase>false</ignorePasswordCase> * <ignoreUserCase>false</ignoreUserCase> * <p/> * <!-- Configure the proper store here --> * <credential-store> * ... * </credential-store> * <p/> * <credential-store-key-adapter> * ... * </credential-store-key-adapter> * <p/> * </authentication-scheme> * <p/> * </pre> * * @org.apache.xbean.XBean element="basic-auth-scheme" * * @author <a href="mailto:sgonzalez@josso.org">Sebastian Gonzalez Oyuela</a> * @version $Id: UsernamePasswordAuthScheme.java 568 2008-07-31 18:39:20Z sgonzalez $ * @see org.josso.auth.CredentialStore * @see org.josso.gateway.identity.service.store.AbstractStore * @see UsernamePasswordCredentialProvider */ public class UsernamePasswordAuthScheme extends AbstractAuthenticationScheme { private static final Log logger = LogFactory.getLog(UsernamePasswordAuthScheme.class); private String _hashAlgorithm; private String _hashEncoding; private String _hashCharset; private boolean _ignorePasswordCase; private boolean _ignoreUserCase; // Some spetial configuration attributes, /** * This attribute is only used when CRYPT hasing is configued. The default value is 2. */ private int _saltLenght = 2; public UsernamePasswordAuthScheme() { this.setName("basic-authentication"); } /** * The username recieved as UsernameCredential instance, if any. */ public Principal getPrincipal() { return new SimplePrincipal(getUsername(_inputCredentials)); } /** * The username recieved as UsernameCredential instance, if any. */ public Principal getPrincipal(Credential[] credentials) { return new SimplePrincipal(getUsername(credentials)); } /** * Authenticates the user using recieved credentials to proof his identity. * * @return the Principal if credentials are valid, null otherwise. */ public boolean authenticate() throws SSOAuthenticationException { setAuthenticated(false); String username = getUsername(_inputCredentials); String password = getPassword(_inputCredentials); // Check if all credentials are present. if (username == null || username.length() == 0 || password == null || password.length() == 0) { if (logger.isDebugEnabled()) { logger.debug("Username " + (username == null || username.length() == 0 ? " not" : "") + " provided. " + "Password " + (password == null || password.length() == 0 ? " not" : "") + " provided."); } // We don't support empty values ! return false; } String knownUsername = getUsername(getKnownCredentials()); String expectedPassword = getPassword(getKnownCredentials()); // We might have to hash the password. password = createPasswordHash(password); // Validate user identity ... if (!validateUsername(username, knownUsername) || !validatePassword(password, expectedPassword)) { return false; } // set valid username so rememberMe token can be created from valid user information if (_ignoreUserCase && !username.equals(knownUsername)) { updateUsername(_inputCredentials, knownUsername); } if (logger.isDebugEnabled()) logger.debug("[authenticate()], Principal authenticated : " + username); // We have successfully authenticated this user. setAuthenticated(true); return true; } /** * Only one password credential supported. */ public Credential[] getPrivateCredentials() { Credential c = getPasswordCredential(_inputCredentials); if (c == null) return new Credential[0]; Credential[] r = {c}; return r; } /** * Only one username credential supported. */ public Credential[] getPublicCredentials() { Credential c = getUsernameCredential(_inputCredentials); if (c == null) return new Credential[0]; Credential[] r = {c}; return r; } @Override public Credential newEncodedCredential(String name, Object value) { try { String v = (String) value; if (name.equals(UsernamePasswordCredentialProvider.PASSWORD_CREDENTIAL_NAME)) v = createPasswordHash(v); return super.newEncodedCredential(name, v); } catch (SSOAuthenticationException e) { logger.error("Cannot create encoded credential " + e.getMessage(), e); return null; } } // -------------------------------------------------------------------- // Protected utils // -------------------------------------------------------------------- /** * This method validates the input password agaist the expected password. * * @param inputPassword * @param expectedPassword */ protected boolean validatePassword(String inputPassword, String expectedPassword) { if (logger.isDebugEnabled()) logger.debug("Validating passwords [" + inputPassword + "/" + expectedPassword + "]"); // Validate input and expected passwords. if (inputPassword == null && expectedPassword == null) return false; if (_ignorePasswordCase && _hashAlgorithm == null) return inputPassword.equalsIgnoreCase(expectedPassword); else return inputPassword.equals(expectedPassword); } /** * This method validates the input password agaist the expected password. * * @param inputUsername * @param expectedUsername */ protected boolean validateUsername(String inputUsername, String expectedUsername) { if (logger.isDebugEnabled()) logger.debug("Validating usernames [" + inputUsername + "/" + expectedUsername + "]"); if (inputUsername == null && expectedUsername == null) return false; if (_ignoreUserCase) return inputUsername.equalsIgnoreCase(expectedUsername); else return inputUsername.equals(expectedUsername); } /** * This method allows password hashing. * In order to work, you need to specify hashAlgorithm and hashEncoding properties. * You can optionally set hashCharset property. * * @return the hashed password. */ protected String createPasswordHash(String password) throws SSOAuthenticationException { // If none of this properties are set, do nothing ... if (getHashAlgorithm() == null && getHashEncoding() == null) { // Nothing to do ... return password; } if (logger.isDebugEnabled()) logger.debug("Creating password hash for [" + password + "] with algorithm/encoding [" + getHashAlgorithm() + "/" + getHashEncoding() + "]"); // Check for spetial encryption mechanisms, not supported by the JDK if ("CRYPT".equalsIgnoreCase(getHashAlgorithm())) { // Get known password String knownPassword = getPassword(getKnownCredentials()); String salt = knownPassword != null && knownPassword.length() > 1 ? knownPassword.substring(0, _saltLenght) : ""; return Crypt.crypt(salt, password); } byte[] passBytes; String passwordHash = null; // convert password to byte data try { if (_hashCharset == null) passBytes = password.getBytes(); else passBytes = password.getBytes(_hashCharset); } catch (UnsupportedEncodingException e) { logger.error("charset " + _hashCharset + " not found. Using platform default."); passBytes = password.getBytes(); } // calculate the hash and apply the encoding. try { byte[] hash; // Hash algorithm is optional if (_hashAlgorithm != null) hash = getDigest().digest(passBytes); else hash = passBytes; // At this point, hashEncoding is required. if ("BASE64".equalsIgnoreCase(_hashEncoding)) { passwordHash = CipherUtil.encodeBase64(hash); } else if ("HEX".equalsIgnoreCase(_hashEncoding)) { passwordHash = CipherUtil.encodeBase16(hash); } else if (_hashEncoding == null) { logger.error("You must specify a hashEncoding when using hashAlgorithm"); } else { logger.error("Unsupported hash encoding format " + _hashEncoding); } } catch (Exception e) { logger.error("Password hash calculation failed : \n" + e.getMessage() != null ? e.getMessage() : e.toString(), e); } return passwordHash; } /** * Only invoke this if algorithm is set. * * @throws SSOAuthenticationException */ protected MessageDigest getDigest() throws SSOAuthenticationException { MessageDigest _digest = null; if (_hashAlgorithm != null) { try { _digest = MessageDigest.getInstance(_hashAlgorithm); logger.debug("Using hash algorithm/encoding : " + _hashAlgorithm + "/" + _hashEncoding); } catch (NoSuchAlgorithmException e) { logger.error("Algorithm not supported : " + _hashAlgorithm, e); throw new SSOAuthenticationException(e.getMessage(), e); } } return _digest; } /** * Gets the username from the received credentials. * * @param credentials */ protected String getUsername(Credential[] credentials) { UsernameCredential c = getUsernameCredential(credentials); if (c == null) return null; return (String) c.getValue(); } /** * Gets the password from the recevied credentials. * * @param credentials */ protected String getPassword(Credential[] credentials) { PasswordCredential p = getPasswordCredential(credentials); if (p == null) return null; return (String) p.getValue(); } /** * Updates the username of the received credentials. * * @param credentials */ protected void updateUsername(Credential[] credentials, String username) { UsernameCredential c = getUsernameCredential(credentials); if (c != null) { c.setValue(username); } } /** * Gets the credential that represents a password. * * @param credentials */ protected PasswordCredential getPasswordCredential(Credential[] credentials) { for (int i = 0; i < credentials.length; i++) { if (credentials[i] instanceof PasswordCredential) { return (PasswordCredential) credentials[i]; } } return null; } /** * Gets the credential that represents a Username. */ protected UsernameCredential getUsernameCredential(Credential[] credentials) { for (int i = 0; i < credentials.length; i++) { if (credentials[i] instanceof UsernameCredential) { return (UsernameCredential) credentials[i]; } } return null; } protected CredentialProvider doMakeCredentialProvider() { return new UsernamePasswordCredentialProvider(); } public String getHashAlgorithm() { return _hashAlgorithm; } public void setHashAlgorithm(String hashAlgorithm) { if (hashAlgorithm != null && hashAlgorithm.equals("")) hashAlgorithm = null; _hashAlgorithm = hashAlgorithm; } /** * Getter for the encoding used for password hashing. * Supported values : HEX, BASE64 */ public String getHashEncoding() { return _hashEncoding; } /** * Setter for the encoding used for password hashing. * Supported values : HEX, BASE64 */ public void setHashEncoding(String hashEnconding) { if (hashEnconding != null && hashEnconding.equals("")) hashEnconding = null; _hashEncoding = hashEnconding; } public String getHashCharset() { return _hashCharset; } public void setHashCharset(String hashCharset) { _hashCharset = hashCharset; } public void setSaltLenght(String saltLenght) { setSaltLength(Integer.valueOf(saltLenght).intValue()); } /** * Only used when CRYPT is configured, default value is 2. */ public int getSaltLength() { return _saltLenght; } public void setSaltLength(int sl) { _saltLenght = sl; } /** * Values : true , false, */ public void setIgnorePasswordCase(String ignorePasswordCase) { _ignorePasswordCase = Boolean.valueOf(ignorePasswordCase).booleanValue(); } /** * Values : true , false, */ public void setIgnoreUserCase(String ignoreUserCase) { _ignoreUserCase = Boolean.valueOf(ignoreUserCase).booleanValue(); } public Object clone() { UsernamePasswordAuthScheme s = (UsernamePasswordAuthScheme) super.clone(); s.setHashAlgorithm(_hashAlgorithm); s.setHashCharset(_hashCharset); s.setHashEncoding(_hashEncoding); s.setIgnorePasswordCase(_ignorePasswordCase + ""); s.setIgnoreUserCase(_ignoreUserCase + ""); s.setName(_name); return s; } }