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 2016/12/12 10:03:36 UTC

svn commit: r1773756 - in /tomcat/trunk: java/org/apache/catalina/realm/ test/org/apache/catalina/realm/ webapps/docs/

Author: markt
Date: Mon Dec 12 10:03:35 2016
New Revision: 1773756

URL: http://svn.apache.org/viewvc?rev=1773756&view=rev
Log:
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=60446
Handle the case where the stored user credential uses a different key length than the length currently configured for the CredentialHandler.
Based on a patch by Niklas Holm.

Modified:
    tomcat/trunk/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java
    tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties
    tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java
    tomcat/trunk/java/org/apache/catalina/realm/SecretKeyCredentialHandler.java
    tomcat/trunk/test/org/apache/catalina/realm/TestMessageDigestCredentialHandler.java
    tomcat/trunk/test/org/apache/catalina/realm/TestSecretKeyCredentialHandler.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java?rev=1773756&r1=1773755&r2=1773756&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java (original)
+++ tomcat/trunk/java/org/apache/catalina/realm/DigestCredentialHandlerBase.java Mon Dec 12 10:03:35 2016
@@ -126,6 +126,13 @@ public abstract class DigestCredentialHa
 
         String serverCredential = mutate(userCredential, salt, iterations);
 
+        // Failed to generate server credential from user credential. Points to
+        // a configuration issue. The root cause should have been logged in the
+        // mutate() method.
+        if (serverCredential == null) {
+            return null;
+        }
+
         if (saltLength == 0 && iterations == 1) {
             // Output the simple/old format for backwards compatibility
             return serverCredential;
@@ -155,6 +162,13 @@ public abstract class DigestCredentialHa
     protected boolean matchesSaltIterationsEncoded(String inputCredentials,
             String storedCredentials) {
 
+        if (storedCredentials == null) {
+            // Stored credentials are invalid
+            // This may be expected if nested credential handlers are being used
+            logInvalidStoredCredentials(storedCredentials);
+            return false;
+        }
+
         int sep1 = storedCredentials.indexOf('$');
         int sep2 = storedCredentials.indexOf('$', sep1 + 1);
 
@@ -178,7 +192,13 @@ public abstract class DigestCredentialHa
             return false;
         }
 
-        String inputHexEncoded = mutate(inputCredentials, salt, iterations);
+        String inputHexEncoded = mutate(inputCredentials, salt, iterations,
+                HexUtils.fromHexString(storedHexEncoded).length * Byte.SIZE);
+        if (inputHexEncoded == null) {
+            // Failed to mutate user credentials. Automatic fail.
+            // Root cause should be logged by mutate()
+            return false;
+        }
 
         return storedHexEncoded.equalsIgnoreCase(inputHexEncoded);
     }
@@ -204,7 +224,8 @@ public abstract class DigestCredentialHa
 
     /**
      * Generates the equivalent stored credentials for the given input
-     * credentials, salt and iterations.
+     * credentials, salt and iterations. If the algorithm requires a key length,
+     * the default will be used.
      *
      * @param inputCredentials  User provided credentials
      * @param salt              Salt, if any
@@ -214,10 +235,35 @@ public abstract class DigestCredentialHa
      *                          stored credentials
      *
      * @return  The equivalent stored credentials for the given input
-     *          credentials
+     *          credentials or <code>null</code> if the generation fails
      */
     protected abstract String mutate(String inputCredentials, byte[] salt, int iterations);
 
+
+    /**
+     * Generates the equivalent stored credentials for the given input
+     * credentials, salt, iterations and key length. The default implementation
+     * calls ignores the key length and calls
+     * {@link #mutate(String, byte[], int)}. Sub-classes that use the key length
+     * should override this method.
+     *
+     * @param inputCredentials  User provided credentials
+     * @param salt              Salt, if any
+     * @param iterations        Number of iterations of the algorithm associated
+     *                          with this CredentialHandler applied to the
+     *                          inputCredentials to generate the equivalent
+     *                          stored credentials
+     * @param keyLength         Length of the produced digest in bits for
+     *                          implementations where it's applicable
+     *
+     * @return  The equivalent stored credentials for the given input
+     *          credentials or <code>null</code> if the generation fails
+     */
+    protected String mutate(String inputCredentials, byte[] salt, int iterations, int keyLength) {
+        return mutate(inputCredentials, salt, iterations);
+    }
+
+
     /**
      * Set the algorithm used to convert input credentials to stored
      * credentials.

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=1773756&r1=1773755&r2=1773756&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/catalina/realm/LocalStrings.properties Mon Dec 12 10:03:35 2016
@@ -93,5 +93,6 @@ combinedRealm.realmStartFail=Failed to s
 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
 credentialHandler.invalidStoredCredential=The invalid stored credential string [{0}] was provided by the Realm to match with the user provided credentials
+credentialHandler.unableToMutateUserCredential=Failed to mutate user provided credentials. This typically means the CredentialHandler configuration is invalid
 mdCredentialHandler.unknownEncoding=The encoding [{0}] is not supported so the current setting of [{1}] will still be used
 pbeCredentialHandler.invalidKeySpec=Unable to generate a password based key
\ No newline at end of file

Modified: 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=1773756&r1=1773755&r2=1773756&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java (original)
+++ tomcat/trunk/java/org/apache/catalina/realm/MessageDigestCredentialHandler.java Mon Dec 12 10:03:35 2016
@@ -148,6 +148,11 @@ public class MessageDigestCredentialHand
             } else {
                 // Hex hashes should be compared case-insensitively
                 String userDigest = mutate(inputCredentials, null, 1);
+                if (userDigest == null) {
+                    // Failed to mutate user credentials. Automatic fail.
+                    // Root cause should be logged by mutate()
+                    return false;
+                }
                 return storedCredentials.equalsIgnoreCase(userDigest);
             }
         }

Modified: tomcat/trunk/java/org/apache/catalina/realm/SecretKeyCredentialHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/realm/SecretKeyCredentialHandler.java?rev=1773756&r1=1773755&r2=1773756&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/realm/SecretKeyCredentialHandler.java (original)
+++ tomcat/trunk/java/org/apache/catalina/realm/SecretKeyCredentialHandler.java Mon Dec 12 10:03:35 2016
@@ -76,12 +76,17 @@ public class SecretKeyCredentialHandler
 
     @Override
     protected String mutate(String inputCredentials, byte[] salt, int iterations) {
-        KeySpec spec = new PBEKeySpec(inputCredentials.toCharArray(), salt, iterations, getKeyLength());
+        return mutate(inputCredentials, salt, iterations, getKeyLength());
+    }
+
 
+    @Override
+    protected String mutate(String inputCredentials, byte[] salt, int iterations, int keyLength) {
         try {
+            KeySpec spec = new PBEKeySpec(inputCredentials.toCharArray(), salt, iterations, keyLength);
             return HexUtils.toHexString(secretKeyFactory.generateSecret(spec).getEncoded());
-        } catch (InvalidKeySpecException e) {
-            log.warn("pbeCredentialHandler.invalidKeySpec", e);
+        } catch (InvalidKeySpecException | IllegalArgumentException e) {
+            log.warn(sm.getString("pbeCredentialHandler.invalidKeySpec"), e);
             return null;
         }
     }

Modified: tomcat/trunk/test/org/apache/catalina/realm/TestMessageDigestCredentialHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/realm/TestMessageDigestCredentialHandler.java?rev=1773756&r1=1773755&r2=1773756&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/realm/TestMessageDigestCredentialHandler.java (original)
+++ tomcat/trunk/test/org/apache/catalina/realm/TestMessageDigestCredentialHandler.java Mon Dec 12 10:03:35 2016
@@ -49,10 +49,12 @@ public class TestMessageDigestCredential
 
     private void doTest(String digest, int saltLength, int iterations) throws NoSuchAlgorithmException {
         MessageDigestCredentialHandler mdch = new MessageDigestCredentialHandler();
+        MessageDigestCredentialHandler verifier = new MessageDigestCredentialHandler();
         mdch.setAlgorithm(digest);
         mdch.setIterations(iterations);
         mdch.setSaltLength(saltLength);
+        verifier.setAlgorithm(digest);
         String storedCredential = mdch.mutate(PWD);
-        Assert.assertTrue(mdch.matches(PWD, storedCredential));
+        Assert.assertTrue(verifier.matches(PWD, storedCredential));
     }
 }

Modified: tomcat/trunk/test/org/apache/catalina/realm/TestSecretKeyCredentialHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/realm/TestSecretKeyCredentialHandler.java?rev=1773756&r1=1773755&r2=1773756&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/realm/TestSecretKeyCredentialHandler.java (original)
+++ tomcat/trunk/test/org/apache/catalina/realm/TestSecretKeyCredentialHandler.java Mon Dec 12 10:03:35 2016
@@ -23,29 +23,62 @@ import org.junit.Test;
 
 public class TestSecretKeyCredentialHandler {
 
-    private static final String[] ALGORITHMS =
-            new String[] {"PBKDF2WithHmacSHA1", "PBEWithMD5AndDES"};
-
-    private static final String PWD = "password";
+    private static final String[] ALGORITHMS = { "PBKDF2WithHmacSHA1", "PBEWithMD5AndDES" };
+    private static final String[] PASSWORDS = { "password", "$!&#%!%@$#@*^$%&%%#!!*%$%&#@!^" };
+    private static final int[] KEYLENGTHS = { 8, 111, 256 };
+    private static final int[] SALTLENGTHS = { 1, 7, 12, 20 };
+    private static final int[] ITERATIONS = { 1, 2111, 10000 };
 
     @Test
     public void testGeneral() throws Exception {
         for (String digest : ALGORITHMS) {
-            for (int saltLength = 1; saltLength < 20; saltLength++) {
-                for (int iterations = 1; iterations < 10000; iterations += 1000)
-                    doTest(digest, saltLength, iterations);
+            for (String password : PASSWORDS) {
+                for (int saltLength : SALTLENGTHS) {
+                    for (int iterations : ITERATIONS) {
+                        for (int keyLength : KEYLENGTHS) {
+                            doTest(password, digest, saltLength, iterations, keyLength, true);
+                        }
+                    }
+                }
             }
         }
     }
 
-    private void doTest(String digest, int saltLength, int iterations)
-            throws NoSuchAlgorithmException {
+    @Test
+    public void testZeroSalt() throws NoSuchAlgorithmException {
+        doTest(PASSWORDS[0], ALGORITHMS[0], 0, ITERATIONS[0], KEYLENGTHS[0], false);
+    }
+
+    @Test
+    public void testZeroIterations() throws NoSuchAlgorithmException {
+        doTest(PASSWORDS[0], ALGORITHMS[0], SALTLENGTHS[0], 0, KEYLENGTHS[0], false);
+    }
+
+    @Test
+    public void testZeroKeyLength() throws NoSuchAlgorithmException {
+        doTest(PASSWORDS[0], ALGORITHMS[0], SALTLENGTHS[0], ITERATIONS[0], 0, false);
+    }
+
+    private void doTest(String password, String digest, int saltLength, int iterations,
+            int keyLength, boolean expectMatch) throws NoSuchAlgorithmException {
         SecretKeyCredentialHandler pbech = new SecretKeyCredentialHandler();
+        SecretKeyCredentialHandler verifier = new SecretKeyCredentialHandler();
         pbech.setAlgorithm(digest);
         pbech.setIterations(iterations);
         pbech.setSaltLength(saltLength);
-        String storedCredential = pbech.mutate(PWD);
-        Assert.assertTrue("[" + digest + "] [" + saltLength + "] [" + iterations + "] [" + PWD +
-                "] [" + storedCredential +"]", pbech.matches(PWD, storedCredential));
+        pbech.setKeyLength(keyLength);
+        verifier.setAlgorithm(digest);
+        String storedCredential = pbech.mutate(password);
+        if (expectMatch) {
+            Assert.assertTrue(
+                    "[" + digest + "] [" + saltLength + "] [" + iterations + "] [" + keyLength + "] ["
+                            + password + "] [" + storedCredential + "]",
+                    verifier.matches(password, storedCredential));
+        } else {
+            Assert.assertFalse(
+                    "[" + digest + "] [" + saltLength + "] [" + iterations + "] [" + keyLength + "] ["
+                            + password + "] [" + storedCredential + "]",
+                    verifier.matches(password, storedCredential));
+        }
     }
 }

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1773756&r1=1773755&r2=1773756&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Mon Dec 12 10:03:35 2016
@@ -45,6 +45,15 @@
   issues do not "pop up" wrt. others).
 -->
 <section name="Tomcat 9.0.0.M16 (markt)" rtext="in development">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>60446</bug>: Handle the case where the stored user credential uses
+        a different key length than the length currently configured for the
+        <code>CredentialHandler</code>. Based on a patch by Niklas Holm. (markt)
+      </fix>
+    </changelog>
+  </subsection>
   <subsection name="Coyote">
     <changelog>
       <fix>



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org