From 4068b152e530305f588bd56e753045936e87ede0 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 5 Nov 2019 13:06:07 +0100 Subject: [PATCH] Add a PasswordHash class as a central place to deal with password hashes. Instead of having to deal with the implementation details of hashing and verifying passwords in multiple places, have a central unit be responsible for it. Otherwise we need to edit three different places when adding a new hashing scheme. With this class adding a new hashing scheme just requires creating a new subclass of `PasswordHash` and registering its type in the enum `PasswordHash.Type`. The rest of the code will use a common interface for all hashing schemes and doesn't need to be changed when a new one is added. --- .../java/com/gitblit/utils/PasswordHash.java | 220 +++++++++ .../com/gitblit/utils/PasswordHashTest.java | 420 ++++++++++++++++++ 2 files changed, 640 insertions(+) create mode 100644 src/main/java/com/gitblit/utils/PasswordHash.java create mode 100644 src/test/java/com/gitblit/utils/PasswordHashTest.java diff --git a/src/main/java/com/gitblit/utils/PasswordHash.java b/src/main/java/com/gitblit/utils/PasswordHash.java new file mode 100644 index 00000000..5ccfab49 --- /dev/null +++ b/src/main/java/com/gitblit/utils/PasswordHash.java @@ -0,0 +1,220 @@ +/* + * Copyright 2017 gitblit.com. + * + * Licensed 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 com.gitblit.utils; + +/** + * This is the superclass for classes responsible for handling password hashing. + * + * It provides a factory-like interface to create an instance of a class that + * is responsible for the mechanics of a specific password hashing method. + * It also provides the common interface, leaving implementation specifics + * to subclasses of itself, which are the factory products. + * + * @author Florian Zschocke + * @since 1.9.0 + */ +public abstract class PasswordHash { + + + /** + * The types of implemented password hashing schemes. + */ + enum Type { + MD5, + CMD5 + } + + /** + * The hashing scheme type handled by an instance of a subclass + */ + final Type type; + + + /** + * Constructor for subclasses to initialize the final type field. + * @param type + * Type of hashing scheme implemented by this instance. + */ + PasswordHash(Type type) { + this.type = type; + } + + + /** + * Create an instance of a password hashing class for the given hash type. + * + * @param type + * Type of hash to be used. + * @return A class that can calculate the given hash type and verify a user password, + * or null if the given hash type is not a valid one. + */ + public static PasswordHash instanceOf(String type) { + try { + Type hashType = Type.valueOf(type.toUpperCase()); + switch (hashType) { + case MD5: + return new PasswordHashMD5(); + case CMD5: + return new PasswordHashCombinedMD5(); + default: + return null; + } + } + catch (Exception e) { + return null; + } + } + + /** + * Create an instance of a password hashing class of the correct type for a given + * hashed password from the user password table. The stored hashed password needs + * to be prefixed with the hash type identifier. + * + * @param hashedEntry + * Hashed password string from the user table. + * @return + * A class that can calculate the given hash type and verify a user password, + * or null if no instance can be created for the hashed user password. + */ + public static PasswordHash instanceFor(String hashedEntry) { + Type type = getEntryType(hashedEntry); + if (type != null) return instanceOf(type.name()); + return null; + } + + /** + * Test if a given string is a hashed password entry. This method simply checks if the + * given string is prefixed by a known hash type identifier. + * + * @param password + * A stored user password. + * @return True if the given string is detected to be hashed with a known hash type, + * false otherwise. + */ + public static boolean isHashedEntry(String password) { + return null != getEntryType(password); + } + + + + /** + * Convert the given password to a hashed password entry to be stored in the user table. + * The resulting string is prefixed by the hashing scheme type followed by a colon: + * TYPE:theactualhashinhex + * + * @param password + * Password to be hashed. + * @param username + * User name, only used for the Combined-MD5 (user+MD5) hashing type. + * @return + * Hashed password entry to be stored in the user table. + */ + abstract public String toHashedEntry(String password, String username); + + /** + * Test if a given password (and user name) match a hashed password. + * The instance of the password hash class has to be created with + * {code instanceFor}, so that it matches the type of the hashed password + * entry to test against. + * + * + * @param hashedEntry + * The hashed password entry from the user password table. + * @param password + * Clear text password to test against the hashed one. + * @param username + * User name, needed for the MD5+USER hash type. + * @return True, if the password (and username) match the hashed password, + * false, otherwise. + */ + public boolean matches(String hashedEntry, char[] password, String username) { + if (hashedEntry == null || type != PasswordHash.getEntryType(hashedEntry)) return false; + if (password == null) return false; + + String hashed = toHashedEntry(String.valueOf(password), username); + return hashed.equalsIgnoreCase(hashedEntry); + } + + + + + + static Type getEntryType(String hashedEntry) { + if (hashedEntry == null) return null; + int indexOfSeparator = hashedEntry.indexOf(':'); + if (indexOfSeparator <= 0) return null; + String typeId = hashedEntry.substring(0, indexOfSeparator); + + try { + return Type.valueOf(typeId.toUpperCase()); + } + catch (Exception e) { return null;} + } + + + static String getEntryValue(String hashedEntry) { + if (hashedEntry == null) return null; + int indexOfSeparator = hashedEntry.indexOf(':'); + return hashedEntry.substring(indexOfSeparator +1, hashedEntry.length()); + } + + + + + + /************************************** Implementations *************************************************/ + + private static class PasswordHashMD5 extends PasswordHash + { + PasswordHashMD5() { + super(Type.MD5); + } + + @Override + public String toHashedEntry(String password, String username) { + if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); + return type.name() + ":" + + StringUtils.getMD5(password); + } + } + + + + + private static class PasswordHashCombinedMD5 extends PasswordHash + { + PasswordHashCombinedMD5() { + super(Type.CMD5); + } + + @Override + public String toHashedEntry(String password, String username) { + if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password with Combined-MD5."); + if (username == null) throw new IllegalArgumentException("The username argument may not be null when hashing a password with Combined-MD5."); + if (StringUtils.isEmpty(username)) throw new IllegalArgumentException("The username argument may not be empty when hashing a password with Combined-MD5."); + return type.name() + ":" + + StringUtils.getMD5(username.toLowerCase() + password); + } + + @Override + public boolean matches(String hashedEntry, char[] password, String username) { + if (username == null || StringUtils.isEmpty(username)) return false; + return super.matches(hashedEntry, password, username); + } + + } +} diff --git a/src/test/java/com/gitblit/utils/PasswordHashTest.java b/src/test/java/com/gitblit/utils/PasswordHashTest.java new file mode 100644 index 00000000..12686692 --- /dev/null +++ b/src/test/java/com/gitblit/utils/PasswordHashTest.java @@ -0,0 +1,420 @@ +/* + * Copyright 2017 gitblit.com. + * + * Licensed 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 com.gitblit.utils; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Florian Zschocke + * + */ +public class PasswordHashTest { + + static final String MD5_PASSWORD_0 = "password"; + static final String MD5_HASHED_ENTRY_0 = "MD5:5F4DCC3B5AA765D61D8327DEB882CF99"; + static final String MD5_PASSWORD_1 = "This is a test password"; + static final String MD5_HASHED_ENTRY_1 = "md5:8e1901831af502c0f842d4efb9083bcf"; + static final String CMD5_USERNAME_0 = "Jane Doe"; + static final String CMD5_PASSWORD_0 = "password"; + static final String CMD5_HASHED_ENTRY_0 = "CMD5:DB9639A6E5F21457F9DFD7735FAFA68B"; + static final String CMD5_USERNAME_1 = "Joe Black"; + static final String CMD5_PASSWORD_1 = "ThisIsAWeirdScheme.Weird"; + static final String CMD5_HASHED_ENTRY_1 = "cmd5:5c154768287e32fa605656b98894da89"; + + + /** + * Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for MD5. + */ + @Test + public void testInstanceOfMD5() { + + PasswordHash pwdh = PasswordHash.instanceOf("md5"); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.MD5, pwdh.type); + assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); + + pwdh = PasswordHash.instanceOf("MD5"); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.MD5, pwdh.type); + assertTrue("Failed to match " +MD5_HASHED_ENTRY_0, pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); + + pwdh = PasswordHash.instanceOf("mD5"); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.MD5, pwdh.type); + assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); + + + pwdh = PasswordHash.instanceOf("CMD5"); + assertNotNull(pwdh); + assertNotEquals(PasswordHash.Type.MD5, pwdh.type); + assertFalse("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); + } + + + + /** + * Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for combined MD5. + */ + @Test + public void testInstanceOfCombinedMD5() { + + PasswordHash pwdh = PasswordHash.instanceOf("cmd5"); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.CMD5, pwdh.type); + assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); + + pwdh = PasswordHash.instanceOf("cMD5"); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.CMD5, pwdh.type); + assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); + + pwdh = PasswordHash.instanceOf("CMD5"); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.CMD5, pwdh.type); + assertTrue("Failed to match " +CMD5_HASHED_ENTRY_0, pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); + + + pwdh = PasswordHash.instanceOf("MD5"); + assertNotNull(pwdh); + assertNotEquals(PasswordHash.Type.CMD5, pwdh.type); + assertFalse("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); + } + + + + + + + + /** + * Test that no instance is returned for plaintext or unknown or not + * yet implemented hashing schemes. + */ + @Test + public void testNoInstanceOf() { + PasswordHash pwdh = PasswordHash.instanceOf("plain"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceOf("PLAIN"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceOf("Plain"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceOf("scrypt"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceOf("bCrypt"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceOf("BCRYPT"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceOf("nixe"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceOf(null); + assertNull(pwdh); + } + + + + /** + * Test that for all known hash types an instance is created for a hashed entry + * that can verify the known password. + * + * Test method for {@link com.gitblit.utils.PasswordHash#instanceFor(java.lang.String)}. + */ + @Test + public void testInstanceFor() { + PasswordHash pwdh = PasswordHash.instanceFor(MD5_HASHED_ENTRY_0); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.MD5, pwdh.type); + assertTrue("Failed to match " +MD5_HASHED_ENTRY_0, pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); + + pwdh = PasswordHash.instanceFor(MD5_HASHED_ENTRY_1); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.MD5, pwdh.type); + assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); + + + pwdh = PasswordHash.instanceFor(CMD5_HASHED_ENTRY_0); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.CMD5, pwdh.type); + assertTrue("Failed to match " +CMD5_HASHED_ENTRY_0, pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); + + pwdh = PasswordHash.instanceFor(CMD5_HASHED_ENTRY_1); + assertNotNull(pwdh); + assertEquals(PasswordHash.Type.CMD5, pwdh.type); + assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); + + + } + + /** + * Test that for no instance is returned for plaintext or unknown or + * not yet implemented hashing schemes. + * + * Test method for {@link com.gitblit.utils.PasswordHash#instanceFor(java.lang.String)}. + */ + @Test + public void testInstanceForNaught() { + PasswordHash pwdh = PasswordHash.instanceFor("password"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceFor("top secret"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceFor("pass:word"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceFor("PLAIN:password"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceFor("SCRYPT:1232rwv12w"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceFor("BCRYPT:urbvahiaufbvhabaiuevuzggubsbliue"); + assertNull(pwdh); + + pwdh = PasswordHash.instanceFor(""); + assertNull(pwdh); + + pwdh = PasswordHash.instanceFor(null); + assertNull(pwdh); + } + + + /** + * Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. + */ + @Test + public void testIsHashedEntry() { + assertTrue(MD5_HASHED_ENTRY_0, PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_0)); + assertTrue(MD5_HASHED_ENTRY_1, PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_1)); + assertTrue(CMD5_HASHED_ENTRY_0, PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_0)); + assertTrue(CMD5_HASHED_ENTRY_1, PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_1)); + + assertFalse(MD5_PASSWORD_1, PasswordHash.isHashedEntry(MD5_PASSWORD_1)); + assertFalse("topsecret", PasswordHash.isHashedEntry("topsecret")); + assertFalse("top:secret", PasswordHash.isHashedEntry("top:secret")); + assertFalse("secret Password", PasswordHash.isHashedEntry("secret Password")); + assertFalse("Empty string", PasswordHash.isHashedEntry("")); + assertFalse("Null", PasswordHash.isHashedEntry(null)); + } + + /** + * Test that hashed entry detection is not case sensitive on the hash type identifier. + * + * Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. + */ + @Test + public void testIsHashedEntryCaseInsenitive() { + assertTrue(MD5_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_1.toLowerCase())); + assertTrue(CMD5_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_1.toLowerCase())); + } + + /** + * Test that unknown or not yet implemented hashing schemes are not detected as hashed entries. + * + * Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. + */ + @Test + public void testIsHashedEntryUnknown() { + assertFalse("BCRYPT:thisismypassword", PasswordHash.isHashedEntry("BCRYPT:thisismypassword")); + assertFalse("TSTHSH:asdchabufzuzfbhbakrzburzbcuzkuzcbajhbcasjdhbckajsbc", PasswordHash.isHashedEntry("TSTHSH:asdchabufzuzfbhbakrzburzbcuzkuzcbajhbcasjdhbckajsbc")); + } + + + + + /** + * Test creating a hashed entry for scheme MD5. In this scheme there is no salt, so a direct + * comparison to a constant value is possible. + * + * Test method for {@link com.gitblit.utils.PasswordHash#toHashedEntry(String, String)} for MD5. + */ + @Test + public void testToHashedEntryMD5() { + PasswordHash pwdh = PasswordHash.instanceOf("MD5"); + String hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_1, null); + assertTrue(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); + + hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_1, "charlie"); + assertTrue(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); + + hashedEntry = pwdh.toHashedEntry("badpassword", "charlie"); + assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); + + hashedEntry = pwdh.toHashedEntry("badpassword", null); + assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); + } + + @Test(expected = IllegalArgumentException.class) + public void testToHashedEntryMD5NullPassword() { + PasswordHash pwdh = PasswordHash.instanceOf("MD5"); + pwdh.toHashedEntry(null, null); + } + + + /** + * Test creating a hashed entry for scheme Combined-MD5. In this scheme there is no salt, so a direct + * comparison to a constant value is possible. + * + * Test method for {@link com.gitblit.utils.PasswordHash#toHashedEntry(String, String)} for CMD5. + */ + @Test + public void testToHashedEntryCMD5() { + PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); + String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, CMD5_USERNAME_1); + assertTrue(CMD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); + + hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, "charlie"); + assertFalse(CMD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); + + hashedEntry = pwdh.toHashedEntry("badpassword", "charlie"); + assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); + } + + @Test(expected = IllegalArgumentException.class) + public void testToHashedEntryCMD5NullPassword() { + PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); + pwdh.toHashedEntry(null, CMD5_USERNAME_1); + } + + /** + * Test creating a hashed entry for scheme Combined-MD5, when no user is given. + * This should never happen in the application, so we expect an exception to be thrown. + * + * Test method for {@link com.gitblit.utils.PasswordHash#toHashedEntry(String, String)} for broken CMD5. + */ + @Test + public void testToHashedEntryCMD5NoUsername() { + PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); + try { + String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, ""); + fail("CMD5 cannot work with an empty '' username. Got: " + hashedEntry); + } + catch (IllegalArgumentException ignored) { /*success*/ } + + try { + String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, " "); + fail("CMD5 cannot work with an empty ' ' username. Got: " + hashedEntry); + } + catch (IllegalArgumentException ignored) { /*success*/ } + + try { + String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, " "); + fail("CMD5 cannot work with an empty ' ' username. Got: " + hashedEntry); + } + catch (IllegalArgumentException ignored) { /*success*/ } + + try { + String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, null); + fail("CMD5 cannot work with a null username. Got: " + hashedEntry); + } + catch (IllegalArgumentException ignored) { /*success*/ } + } + + + + + /** + * Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for MD5. + */ + @Test + public void testMatchesMD5() { + PasswordHash pwdh = PasswordHash.instanceOf("MD5"); + + assertTrue("PWD0, Null user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); + assertTrue("PWD0, Empty user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "")); + assertTrue("PWD0, With user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "maxine")); + + assertTrue("PWD1, Null user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); + assertTrue("PWD1, Empty user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), "")); + assertTrue("PWD1, With user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), "maxine")); + + + + assertFalse("Matched wrong password", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), null)); + assertFalse("Matched wrong password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), " ")); + assertFalse("Matched wrong password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), "someuser")); + + assertFalse("Matched empty password", pwdh.matches(MD5_HASHED_ENTRY_1, "".toCharArray(), null)); + assertFalse("Matched empty password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, " ".toCharArray(), " ")); + assertFalse("Matched empty password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, " ".toCharArray(), "someuser")); + + assertFalse("Matched null password", pwdh.matches(MD5_HASHED_ENTRY_1, null, null)); + assertFalse("Matched null password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, null, " ")); + assertFalse("Matched null password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, null, "someuser")); + + + assertFalse("Matched wrong hashed entry", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), null)); + assertFalse("Matched wrong hashed entry, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), "")); + assertFalse("Matched wrong hashed entry, with user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), "someuser")); + + assertFalse("Matched empty hashed entry", pwdh.matches("", MD5_PASSWORD_0.toCharArray(), null)); + assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", MD5_PASSWORD_0.toCharArray(), "")); + assertFalse("Matched empty hashed entry, with user", pwdh.matches(" ", MD5_PASSWORD_0.toCharArray(), "someuser")); + + assertFalse("Matched null entry", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), null)); + assertFalse("Matched null entry, with empty user", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), "")); + assertFalse("Matched null entry, with user", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), "someuser")); + + + assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); + assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); + } + + /** + * Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for Combined-MD5. + */ + @Test + public void testMatchesCombinedMD5() { + PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); + + assertTrue("PWD0", pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); + assertTrue("Empty user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); + + + + assertFalse("Matched wrong password", pwdh.matches(CMD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), CMD5_USERNAME_1)); + assertFalse("Matched wrong password", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_1)); + + assertFalse("Matched wrong user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_0)); + assertFalse("Matched wrong user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), "Samantha Jones")); + + assertFalse("Matched empty user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), "")); + assertFalse("Matched empty user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), " ")); + assertFalse("Matched null user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), null)); + + assertFalse("Matched empty hashed entry", pwdh.matches("", CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); + assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", CMD5_PASSWORD_1.toCharArray(), "")); + assertFalse("Matched empty hashed entry, with null user", pwdh.matches(" ", CMD5_PASSWORD_0.toCharArray(), null)); + + assertFalse("Matched null entry, with user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); + assertFalse("Matched null entry, with empty user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), "")); + assertFalse("Matched null entry, with null user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), null)); + + + assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), null)); + assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); + assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); + } +} -- 2.39.5