From: Luke T. <lu...@us...> - 2001-12-19 02:47:03
|
User: luke_t Date: 01/12/18 18:47:02 Modified: src/main/org/jboss/security/auth/spi UsernamePasswordLoginModule.java Log: password hashing functionality added. Hasn't been checked thouroughly for compatibily with external tools yet, but shoudn't break any existing code. Revision Changes Path 1.8 +147 -6 jbosssx/src/main/org/jboss/security/auth/spi/UsernamePasswordLoginModule.java Index: UsernamePasswordLoginModule.java =================================================================== RCS file: /cvsroot/jboss/jbosssx/src/main/org/jboss/security/auth/spi/UsernamePasswordLoginModule.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- UsernamePasswordLoginModule.java 2001/12/17 00:07:44 1.7 +++ UsernamePasswordLoginModule.java 2001/12/19 02:47:02 1.8 @@ -8,8 +8,11 @@ package org.jboss.security.auth.spi; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.Map; import java.security.Principal; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; @@ -20,6 +23,8 @@ import javax.security.auth.login.FailedLoginException; import org.jboss.security.SimplePrincipal; +import org.jboss.security.Util; +import org.jboss.security.Base64Encoder; import org.jboss.security.auth.spi.AbstractServerLoginModule; @@ -37,7 +42,8 @@ * @see #getRoleSets() * * @author Sco...@di... - * @version $Revision: 1.7 $ + * @author lu...@mk... + * @version $Revision: 1.8 $ */ public abstract class UsernamePasswordLoginModule extends AbstractServerLoginModule { @@ -50,13 +56,34 @@ /** the principal to use when a null username and password are seen */ private Principal unauthenticatedIdentity; + /** the message digest algorithm used to hash passwords. If null then plain passwords will be used. */ + private String hashAlgorithm = null; + /** + * the name of the charset/encoding to use when converting the password String to a byte array. + * Default is the platform's default encoding. + */ + private String hashCharset = null; + /** the string encoding format to use. Defaults to base64. */ + private String hashEncoding = null; + /** * Override the superclass method to look for an <em>unauthenticatedIdentity</em> * property. This is the name of the principal to assign and authenticate * when a null username and password are seen. * <p> + * The options map will also be checked for the parameter <em>hashAlgorithm</em> + * to see if password hashing should be used instead of plain passwords. + * If you set this parameter, passwords will be hashed using the specified + * algorithm (for example "SHA" or "MD5") and will be encoded as strings. + * <p> + * The string encoding format is set using the optional parameter + * <em>hashEncoding</em>. Currently supported are MIME "BASE64" encoded + * strings as defined in + * (<a href="http://ietf.org/rfc/rfc1521.txt">rfc1521</a>) + * or "HEX" encoded strings. BASE64 is the default if omitted. + * <p> * This method first invokes the super version. - * + * * @param options the map of paramers passed to this login module. */ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) @@ -66,13 +93,28 @@ String name = (String) options.get("unauthenticatedIdentity"); if( name != null ) unauthenticatedIdentity = new SimplePrincipal(name); + + // Check to see if password hashing has been enabled. + // If an algorithm is set, check for a format and charset. + hashAlgorithm = (String) options.get("hashAlgorithm"); + if(hashAlgorithm != null) + { + hashEncoding = (String) options.get("hashEncoding"); + if(hashEncoding == null) + hashEncoding = "BASE64"; + hashCharset = (String) options.get("hashCharset"); + if(log.isDebugEnabled()) + { + log.debug("Passworg hashing activated: algorithm = " + hashAlgorithm + + ", encoding = " + hashEncoding+ (hashCharset == null ? "" : "charset = " + hashCharset)); + } + } } - /** */ public boolean login() throws LoginException { // See if shared credentials exist - if( super.login() == true ) + if( super.login() ) { // Setup our view of the user Object username = sharedState.get("javax.security.auth.login.name"); @@ -102,15 +144,19 @@ if( identity == null ) { identity = new SimplePrincipal(username); + // hash the user entered password if password hashing is in use + if(hashAlgorithm != null) + password = createPasswordHash(username, password); // Validate the password supplied by the subclass String expectedPassword = getUsersPassword(); if( validatePassword(password, expectedPassword) == false ) { - System.out.println("Bad password for username=" + username); + log.warn("Bad password for username = " + username); throw new FailedLoginException("Password Incorrect/Password Required"); } } - System.out.print("User '" + identity + "' authenticated.\n"); + if( log.isInfoEnabled() ) + log.info("User '" + identity + "' authenticated."); if( getUseFirstPass() == true ) { // Add the username and password to the shared state map @@ -187,6 +233,73 @@ return info; } + /** + * If hashing is enabled, this method is called from <code>login()</code> + * prior to password validation. + * <p> + * Subclasses may override it to provide customized password hashing, + * for example by adding user-specific information or salting. + * <p> + * The default version calculates the hash based on the following options: + * <ul> + * <li><em>hashAlgorithm</em>: The digest algorithm to use. + * <li><em>hashEncoding</em>: The format used to store the hashes (base64 or hex) + * <li><em>hashCharset</em>: The encoding used to convert the password to bytes + * for hashing. + * </ul> + * It will return null if the hash fails for any reason, which will in turn + * cause <code>validatePassword()</code> to fail. + * + * @param username ignored in default version + * @param password the password string to be hashed + */ + protected String createPasswordHash(String username, String password) + { + byte[] passBytes; + byte[] hash; + String passwordHash = null; + + // convert password to byte data + try + { + if(hashCharset == null) + passBytes = password.getBytes(); + else + passBytes = password.getBytes(hashCharset); + } + catch(UnsupportedEncodingException uee) + { + log.error("charset " + hashCharset + " not found. Using platform default.", uee); + passBytes = password.getBytes(); + } + + // calculate the hash and apply the encoding. + try + { + hash = MessageDigest.getInstance(hashAlgorithm).digest(passBytes); + if(hashEncoding.equalsIgnoreCase("BASE64")) + { + passwordHash = Base64Encoder.encode(hash); +// test to compare with sun's builtin encoder. +// String sunB64 = new sun.misc.BASE64Encoder().encode(hash); +// System.out.println("Hashes: sun " + sunB64 + ", jboss " + passwordHash); + } + else if(hashEncoding.equalsIgnoreCase("HEX")) + { + passwordHash = hexEncode(hash); + } + else + { + log.error("Unsupported hash encoding format " + hashEncoding); + } + } + catch(Exception e) + { + log.error("Password hash calculation failed ", e); + } + return passwordHash; + } + /** * A hook that allows subclasses to change the validation of the input * password against the expected password. This version checks that @@ -212,4 +325,32 @@ */ abstract protected String getUsersPassword() throws LoginException; +// Private --------------------------------------------------------------------- + /** + * Hex encoding of hashes, as used by Catalina. Each byte is converted to + * the corresponding two hex characters. + */ + private String hexEncode(byte[] bytes) + { + StringBuffer sb = new StringBuffer(bytes.length * 2); + for (int i = 0; i < bytes.length; i++) + { + byte b = bytes[i]; + // top 4 bits + char c = (char)((b >> 4) & 0xf); + if(c > 9) + c = (char)((c - 10) + 'a'); + else + c = (char)(c + '0'); + sb.append(c); + // bottom 4 bits + c = (char)(b & 0xf); + if (c > 9) + c = (char)((c - 10) + 'a'); + else + c = (char)(c + '0'); + sb.append(c); + } + return sb.toString(); + } } |