You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2014/09/25 21:32:13 UTC
svn commit: r1627596 - in /tomcat/trunk: java/org/apache/catalina/
java/org/apache/catalina/realm/ java/org/apache/tomcat/util/security/
test/org/apache/catalina/realm/
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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org