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