Author: markt Date: Thu Sep 25 19:32:13 2014 New Revision: 1627596 URL: http://svn.apache.org/r1627596 Log: First pass at plumbing in a CredentialHandler interface
Added: tomcat/trunk/java/org/apache/catalina/CredentialHandler.java (with props) tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java (with props) Modified: tomcat/trunk/java/org/apache/catalina/Realm.java tomcat/trunk/java/org/apache/catalina/realm/DataSourceRealm.java tomcat/trunk/java/org/apache/catalina/realm/JDBCRealm.java tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties tomcat/trunk/java/org/apache/catalina/realm/MemoryRealm.java tomcat/trunk/java/org/apache/catalina/realm/RealmBase.java tomcat/trunk/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java tomcat/trunk/test/org/apache/catalina/realm/TestRealmBase.java Added: tomcat/trunk/java/org/apache/catalina/CredentialHandler.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/CredentialHandler.java?rev=1627596&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/catalina/CredentialHandler.java (added) +++ tomcat/trunk/java/org/apache/catalina/CredentialHandler.java Thu Sep 25 19:32:13 2014 @@ -0,0 +1,24 @@ +/* + * 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; + +public interface CredentialHandler { + + boolean matches(String inputCredentials, String storedCredentials); + + String mutate(String inputCredentials); +} Propchange: tomcat/trunk/java/org/apache/catalina/CredentialHandler.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/java/org/apache/catalina/Realm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/Realm.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/Realm.java (original) +++ tomcat/trunk/java/org/apache/catalina/Realm.java Thu Sep 25 19:32:13 2014 @@ -188,4 +188,9 @@ public interface Realm { * @param listener The listener to remove */ public void removePropertyChangeListener(PropertyChangeListener listener); + + + public CredentialHandler getCredentialHandler(); + + public void setCredentialHandler(CredentialHandler credentialHandler); } Modified: tomcat/trunk/java/org/apache/catalina/realm/DataSourceRealm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/DataSourceRealm.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/DataSourceRealm.java (original) +++ tomcat/trunk/java/org/apache/catalina/realm/DataSourceRealm.java Thu Sep 25 19:32:13 2014 @@ -294,7 +294,7 @@ public class DataSourceRealm extends Rea String dbCredentials = getPassword(dbConnection, username); // Validate the user's credentials - boolean validated = compareCredentials(credentials, dbCredentials); + boolean validated = getCredentialHandler().matches(credentials, dbCredentials); if (validated) { if (containerLog.isTraceEnabled()) Modified: tomcat/trunk/java/org/apache/catalina/realm/JDBCRealm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/JDBCRealm.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/JDBCRealm.java (original) +++ tomcat/trunk/java/org/apache/catalina/realm/JDBCRealm.java Thu Sep 25 19:32:13 2014 @@ -387,7 +387,7 @@ public class JDBCRealm String dbCredentials = getPassword(username); // Validate the user's credentials - boolean validated = compareCredentials(credentials, dbCredentials); + boolean validated = getCredentialHandler().matches(credentials, dbCredentials); if (validated) { if (containerLog.isTraceEnabled()) Modified: tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java (original) +++ tomcat/trunk/java/org/apache/catalina/realm/JNDIRealm.java Thu Sep 25 19:32:13 2014 @@ -1551,7 +1551,7 @@ public class JNDIRealm extends RealmBase String password = info.getPassword(); - return compareCredentials(credentials, password); + return getCredentialHandler().matches(credentials, password); } Modified: tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties Thu Sep 25 19:32:13 2014 @@ -64,6 +64,7 @@ realmBase.createUsernameRetriever.ClassC realmBase.createUsernameRetriever.ClassNotFoundException=Cannot find class {0}. realmBase.createUsernameRetriever.InstantiationException=Cannot create object of type {0}. realmBase.createUsernameRetriever.IllegalAccessException=Cannot create object of type {0}. +realmBase.credentialHandler.customCredentialHandler=Unable to set the property [{0}] to value [{1}] as a custom CredentialHandler has been configured userDatabaseRealm.lookup=Exception looking up UserDatabase under key {0} userDatabaseRealm.noDatabase=No UserDatabase component found under key {0} dataSourceRealm.authenticateFailure=Username {0} NOT successfully authenticated @@ -82,3 +83,4 @@ combinedRealm.addRealm=Add "{0}" realm, combinedRealm.realmStartFail=Failed to start "{0}" realm lockOutRealm.authLockedUser=An attempt was made to authenticate the locked user "{0}" lockOutRealm.removeWarning=User "{0}" was removed from the failed users cache after {1} seconds to keep the cache size within the limit set +messageDigestCredentialHandler.unknownEncoding=The encoding [{0}] is not supported so the current setting of [{1}] will still be used Modified: tomcat/trunk/java/org/apache/catalina/realm/MemoryRealm.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/MemoryRealm.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/MemoryRealm.java (original) +++ tomcat/trunk/java/org/apache/catalina/realm/MemoryRealm.java Thu Sep 25 19:32:13 2014 @@ -117,7 +117,7 @@ public class MemoryRealm extends RealmB if (principal == null) { validated = false; } else { - validated = compareCredentials(credentials, principal.getPassword()); + validated = getCredentialHandler().matches(credentials, principal.getPassword()); } if (validated) { Added: tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java?rev=1627596&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java (added) +++ tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java Thu Sep 25 19:32:13 2014 @@ -0,0 +1,147 @@ +/* + * 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.realm; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import org.apache.catalina.CredentialHandler; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.buf.B2CConverter; +import org.apache.tomcat.util.buf.HexUtils; +import org.apache.tomcat.util.codec.binary.Base64; +import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.util.security.ConcurrentMessageDigest; + +public class MessageDigestCredentialHandler implements CredentialHandler { + + private static final Log log = LogFactory.getLog(MessageDigestCredentialHandler.class); + protected static final StringManager sm = StringManager.getManager(Constants.Package); + + + private Charset encoding = StandardCharsets.UTF_8; + private String digest = null; + + + public String getEncoding() { + return encoding.name(); + } + + + public void setEncoding(String encodingName) { + if (encodingName == null) { + encoding = StandardCharsets.UTF_8; + } else { + try { + this.encoding = B2CConverter.getCharset(encodingName); + } catch (UnsupportedEncodingException e) { + log.warn(sm.getString("mdCredentialHandler.unknownEncoding=.unknownEncoding", + encodingName, encoding.name())); + } + } + } + + + public String getDigest() { + return digest; + } + + + public void setDigest(String digest) { + try { + MessageDigest.getInstance(digest); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + this.digest = digest; + } + + + @Override + public boolean matches(String inputCredentials, String storedCredentials) { + + if (inputCredentials == null || storedCredentials == null) { + return false; + } + + if (getDigest() == null) { + // No digests, compare directly + return storedCredentials.equals(inputCredentials); + } else { + // Some directories and databases prefix the password with the hash + // type. The string is in a format compatible with Base64.encode not + // the normal hex encoding of the digest + if (storedCredentials.startsWith("{MD5}") || + storedCredentials.startsWith("{SHA}")) { + // Server is storing digested passwords with a prefix indicating + // the digest type + String serverDigest = storedCredentials.substring(5); + String userDigest = Base64.encodeBase64String(ConcurrentMessageDigest.digest( + getDigest(), inputCredentials.getBytes(StandardCharsets.ISO_8859_1))); + return userDigest.equals(serverDigest); + + } else if (storedCredentials.startsWith("{SSHA}")) { + // Server is storing digested passwords with a prefix indicating + // the digest type and the salt used when creating that digest + + String serverDigestPlusSalt = storedCredentials.substring(6); + + // Need to convert the salt to bytes to apply it to the user's + // digested password. + byte[] serverDigestPlusSaltBytes = + Base64.decodeBase64(serverDigestPlusSalt); + final int saltPos = 20; + byte[] serverDigestBytes = new byte[saltPos]; + System.arraycopy(serverDigestPlusSaltBytes, 0, + serverDigestBytes, 0, saltPos); + final int saltLength = serverDigestPlusSaltBytes.length - saltPos; + byte[] serverSaltBytes = new byte[saltLength]; + System.arraycopy(serverDigestPlusSaltBytes, saltPos, + serverSaltBytes, 0, saltLength); + + // Generate the digested form of the user provided password + // using the salt + byte[] userDigestBytes = ConcurrentMessageDigest.digest(getDigest(), + inputCredentials.getBytes(StandardCharsets.ISO_8859_1), + serverSaltBytes); + + return Arrays.equals(userDigestBytes, serverDigestBytes); + + } else { + // Hex hashes should be compared case-insensitively + String userDigest = mutate(inputCredentials); + return storedCredentials.equalsIgnoreCase(userDigest); + } + } + } + + + @Override + public String mutate(String inputCredentials) { + if (digest == null) { + return inputCredentials; + } else { + return HexUtils.toHexString(ConcurrentMessageDigest.digest(digest, + inputCredentials.getBytes(encoding))); + } + } +} Propchange: tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/java/org/apache/catalina/realm/RealmBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/RealmBase.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/realm/RealmBase.java (original) +++ tomcat/trunk/java/org/apache/catalina/realm/RealmBase.java Thu Sep 25 19:32:13 2014 @@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletRes import org.apache.catalina.Container; import org.apache.catalina.Context; +import org.apache.catalina.CredentialHandler; import org.apache.catalina.Engine; import org.apache.catalina.Host; import org.apache.catalina.LifecycleException; @@ -93,15 +94,24 @@ public abstract class RealmBase extends * Valid values are those accepted for the algorithm name by the * MessageDigest class, or <code>null</code> if no digesting should * be performed. + * + * @deprecated Unused. Will be removed in Tomcat 9.0.x onwards. */ + @Deprecated protected String digest = null; /** * The encoding charset for the digest. + * + * @deprecated Unused. Will be removed in Tomcat 9.0.x onwards. */ + @Deprecated protected String digestEncoding = null; + private CredentialHandler credentialHandler; + + /** * The MessageDigest object for digesting user credentials (passwords). * @@ -164,6 +174,17 @@ public abstract class RealmBase extends // ------------------------------------------------------------- Properties + @Override + public CredentialHandler getCredentialHandler() { + return credentialHandler; + } + + + @Override + public void setCredentialHandler(CredentialHandler credentialHandler) { + this.credentialHandler = credentialHandler; + } + /** * Return the Container with which this Realm has been associated. @@ -211,11 +232,17 @@ public abstract class RealmBase extends /** * Return the digest algorithm used for storing credentials. + * + * @deprecated This will be removed in Tomcat 9.0.x as it has been replaced + * by the CredentialHandler */ + @Deprecated public String getDigest() { - - return digest; - + CredentialHandler ch = credentialHandler; + if (ch instanceof MessageDigestCredentialHandler) { + return ((MessageDigestCredentialHandler) ch).getDigest(); + } + return null; } @@ -223,36 +250,79 @@ public abstract class RealmBase extends * Set the digest algorithm used for storing credentials. * * @param digest The new digest algorithm + * + * @deprecated This will be removed in Tomcat 9.0.x as it has been replaced + * by the CredentialHandler */ + @Deprecated public void setDigest(String digest) { - + CredentialHandler ch = credentialHandler; + if (ch == null) { + ch = new MessageDigestCredentialHandler(); + credentialHandler = ch; + } + if (ch instanceof MessageDigestCredentialHandler) { + ((MessageDigestCredentialHandler) ch).setDigest(digest); + } else { + log.warn(sm.getString("realmBase.credentialHandler.customCredentialHandler", + "digest", digest)); + } this.digest = digest; - } /** * Returns the digest encoding charset. * * @return The charset (may be null) for platform default + * + * @deprecated This will be removed in Tomcat 9.0.x as it has been replaced + * by the CredentialHandler */ + @Deprecated public String getDigestEncoding() { - return digestEncoding; + CredentialHandler ch = credentialHandler; + if (ch instanceof MessageDigestCredentialHandler) { + return ((MessageDigestCredentialHandler) ch).getEncoding(); + } + return null; } /** * Sets the digest encoding charset. * * @param charset The charset (null for platform default) + * + * @deprecated This will be removed in Tomcat 9.0.x as it has been replaced + * by the CredentialHandler */ + @Deprecated public void setDigestEncoding(String charset) { - digestEncoding = charset; + CredentialHandler ch = credentialHandler; + if (ch == null) { + ch = new MessageDigestCredentialHandler(); + credentialHandler = ch; + } + if (ch instanceof MessageDigestCredentialHandler) { + ((MessageDigestCredentialHandler) ch).setEncoding(charset); + } else { + log.warn(sm.getString("realmBase.credentialHandler.customCredentialHandler", + "digestEncoding", charset)); + } + this.digestEncoding = charset; } + + /** + * @deprecated This will be removed in Tomcat 9.0.x as it has been replaced + * by the CredentialHandler + */ + @Deprecated protected Charset getDigestCharset() throws UnsupportedEncodingException { - if (digestEncoding == null) { + String charset = getDigestEncoding(); + if (charset == null) { return StandardCharsets.ISO_8859_1; } else { - return B2CConverter.getCharset(getDigestEncoding()); + return B2CConverter.getCharset(charset); } } @@ -340,7 +410,7 @@ public abstract class RealmBase extends String serverCredentials = getPassword(username); - boolean validated = compareCredentials(credentials, serverCredentials); + boolean validated = getCredentialHandler().matches(credentials, serverCredentials); if (!validated) { if (containerLog.isTraceEnabled()) { containerLog.trace(sm.getString("realmBase.authenticateFailure", @@ -496,6 +566,10 @@ public abstract class RealmBase extends } + /** + * @deprecated Unused. Will be removed in Tomcat 9.0.x onwards. + */ + @Deprecated protected boolean compareCredentials(String userCredentials, String serverCredentials) { @@ -1116,7 +1190,6 @@ public abstract class RealmBase extends protected void startInternal() throws LifecycleException { // Create a MessageDigest instance for credentials, if desired - if (getDigest() != null) { try { md = MessageDigest.getInstance(getDigest()); @@ -1128,6 +1201,10 @@ public abstract class RealmBase extends } + if (credentialHandler == null) { + credentialHandler = new MessageDigestCredentialHandler(); + } + setState(LifecycleState.STARTING); } Modified: tomcat/trunk/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java Thu Sep 25 19:32:13 2014 @@ -60,6 +60,11 @@ public class ConcurrentMessageDigest { } public static byte[] digest(String algorithm, byte[]... input) { + return digest(algorithm, 1, input); + } + + + public static byte[] digest(String algorithm, int rounds, byte[]... input) { Queue<MessageDigest> queue = queues.get(algorithm); if (queue == null) { @@ -77,11 +82,20 @@ public class ConcurrentMessageDigest { } } + // Round 1 for (byte[] bytes : input) { md.update(bytes); } byte[] result = md.digest(); + // Subsequent rounds + if (rounds > 1) { + for (int i = 1; i < rounds; i++) { + md.update(result); + result = md.digest(); + } + } + queue.add(md); return result; Modified: tomcat/trunk/test/org/apache/catalina/realm/TestRealmBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/realm/TestRealmBase.java?rev=1627596&r1=1627595&r2=1627596&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/realm/TestRealmBase.java (original) +++ tomcat/trunk/test/org/apache/catalina/realm/TestRealmBase.java Thu Sep 25 19:32:13 2014 @@ -89,7 +89,9 @@ public class TestRealmBase { Context context = new TesterContext(); TesterMapRealm realm = new TesterMapRealm(); realm.setContainer(context); - realm.setDigest(digest); + MessageDigestCredentialHandler ch = new MessageDigestCredentialHandler(); + ch.setDigest(digest); + realm.setCredentialHandler(ch); realm.start(); realm.addUser(USER1, digestedPassword); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org