Skip to content

Commit bb89a59

Browse files
committed
#850 Add setting specifying which password hashes should be checked
1 parent 0a9afbe commit bb89a59

File tree

6 files changed

+104
-37
lines changed

6 files changed

+104
-37
lines changed

docs/config.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
2-
<!-- File auto-generated on Sun Oct 30 12:57:15 CET 2016. See docs/config/config.tpl.md -->
2+
<!-- File auto-generated on Sun Nov 13 10:33:55 CET 2016. See docs/config/config.tpl.md -->
33

44
## AuthMe Configuration
55
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
@@ -245,10 +245,12 @@ settings:
245245
passwordHash: 'SHA256'
246246
# Salt length for the SALTED2MD5 MD5(MD5(password)+salt)
247247
doubleMD5SaltLength: 8
248-
# If password checking return false, do we need to check with all
249-
# other password algorithm to check an old password?
250-
# AuthMe will update the password to the new password hash
251-
supportOldPasswordHash: false
248+
# If a password check fails, AuthMe will also try to check with the following hash methods.
249+
# Use this setting when you change from one hash method to another.
250+
# AuthMe will update the password to the new hash. Example:
251+
# legacyHashes:
252+
# - 'SHA1'
253+
legacyHashes: []
252254
# Prevent unsafe passwords from being used; put them in lowercase!
253255
# You should always set 'help' as unsafePassword due to possible conflicts.
254256
# unsafePasswords:
@@ -461,4 +463,4 @@ To change settings on a running server, save your changes to config.yml and use
461463

462464
---
463465

464-
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 30 12:57:15 CET 2016
466+
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Nov 13 10:33:55 CET 2016

src/main/java/fr/xephi/authme/security/PasswordSecurity.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import javax.annotation.PostConstruct;
1414
import javax.inject.Inject;
15+
import java.util.Collection;
1516

1617
/**
1718
* Manager class for password-related operations.
@@ -31,7 +32,7 @@ public class PasswordSecurity implements Reloadable {
3132
private Injector injector;
3233

3334
private HashAlgorithm algorithm;
34-
private boolean supportOldAlgorithm;
35+
private Collection<HashAlgorithm> legacyAlgorithms;
3536

3637
/**
3738
* Load or reload the configuration.
@@ -40,7 +41,7 @@ public class PasswordSecurity implements Reloadable {
4041
@Override
4142
public void reload() {
4243
this.algorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
43-
this.supportOldAlgorithm = settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH);
44+
this.legacyAlgorithms = settings.getProperty(SecuritySettings.LEGACY_HASHES);
4445
}
4546

4647
/**
@@ -83,7 +84,7 @@ public boolean comparePassword(String password, HashedPassword hashedPassword, S
8384
EncryptionMethod method = initializeEncryptionMethodWithEvent(algorithm, playerName);
8485
String playerLowerCase = playerName.toLowerCase();
8586
return methodMatches(method, password, hashedPassword, playerLowerCase)
86-
|| supportOldAlgorithm && compareWithAllEncryptionMethods(password, hashedPassword, playerLowerCase);
87+
|| compareWithLegacyHashes(password, hashedPassword, playerLowerCase);
8788
}
8889

8990
/**
@@ -97,14 +98,12 @@ public boolean comparePassword(String password, HashedPassword hashedPassword, S
9798
*
9899
* @return True if there was a password match with another encryption method, false otherwise
99100
*/
100-
private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword, String playerName) {
101-
for (HashAlgorithm algorithm : HashAlgorithm.values()) {
102-
if (!HashAlgorithm.CUSTOM.equals(algorithm)) {
103-
EncryptionMethod method = initializeEncryptionMethod(algorithm);
104-
if (methodMatches(method, password, hashedPassword, playerName)) {
105-
hashPasswordForNewAlgorithm(password, playerName);
106-
return true;
107-
}
101+
private boolean compareWithLegacyHashes(String password, HashedPassword hashedPassword, String playerName) {
102+
for (HashAlgorithm algorithm : legacyAlgorithms) {
103+
EncryptionMethod method = initializeEncryptionMethod(algorithm);
104+
if (methodMatches(method, password, hashedPassword, playerName)) {
105+
hashPasswordForNewAlgorithm(password, playerName);
106+
return true;
108107
}
109108
}
110109
return false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package fr.xephi.authme.settings;
2+
3+
import com.github.authme.configme.properties.Property;
4+
import com.github.authme.configme.resource.PropertyResource;
5+
6+
import java.util.List;
7+
import java.util.stream.Collectors;
8+
9+
/**
10+
* Property whose value is a set of entries of a given enum.
11+
*/
12+
// TODO https://github.com/AuthMe/ConfigMe/issues/27: Should be a Property of Set<E> type
13+
public class EnumSetProperty<E extends Enum<E>> extends Property<List<E>> {
14+
15+
private final Class<E> enumClass;
16+
17+
public EnumSetProperty(Class<E> enumClass, String path, List<E> defaultValue) {
18+
super(path, defaultValue);
19+
this.enumClass = enumClass;
20+
}
21+
22+
@Override
23+
protected List<E> getFromResource(PropertyResource resource) {
24+
List<?> elements = resource.getList(getPath());
25+
if (elements != null) {
26+
return elements.stream()
27+
.map(val -> toEnum(String.valueOf(val)))
28+
.filter(e -> e != null)
29+
.distinct()
30+
.collect(Collectors.toList());
31+
}
32+
return null;
33+
}
34+
35+
private E toEnum(String str) {
36+
for (E e : enumClass.getEnumConstants()) {
37+
if (str.equalsIgnoreCase(e.name())) {
38+
return e;
39+
}
40+
}
41+
return null;
42+
}
43+
}

src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java

+12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import fr.xephi.authme.ConsoleLogger;
88
import fr.xephi.authme.output.LogLevel;
99
import fr.xephi.authme.settings.properties.PluginSettings;
10+
import fr.xephi.authme.settings.properties.SecuritySettings;
1011

1112
import java.io.File;
1213
import java.io.FileWriter;
@@ -49,6 +50,7 @@ protected boolean performMigrations(PropertyResource resource, List<Property<?>>
4950
| migrateForceSpawnSettings(resource)
5051
| changeBooleanSettingToLogLevelProperty(resource)
5152
| hasOldHelpHeaderProperty(resource)
53+
| hasSupportOldPasswordProperty(resource)
5254
|| hasDeprecatedProperties(resource);
5355
}
5456

@@ -163,6 +165,16 @@ private static boolean hasOldHelpHeaderProperty(PropertyResource resource) {
163165
return false;
164166
}
165167

168+
private static boolean hasSupportOldPasswordProperty(PropertyResource resource) {
169+
String path = "settings.security.supportOldPasswordHash";
170+
if (resource.contains(path)) {
171+
ConsoleLogger.warning("Property '" + path + "' is no longer supported. "
172+
+ "Use '" + SecuritySettings.LEGACY_HASHES.getPath() + "' instead.");
173+
return true;
174+
}
175+
return false;
176+
}
177+
166178
/**
167179
* Checks for an old property path and moves it to a new path if present.
168180
*

src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java

+11-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import com.github.authme.configme.SettingsHolder;
55
import com.github.authme.configme.properties.Property;
66
import fr.xephi.authme.security.HashAlgorithm;
7+
import fr.xephi.authme.settings.EnumSetProperty;
78

9+
import java.util.Collections;
810
import java.util.List;
911

1012
import static com.github.authme.configme.properties.PropertyInitializer.newLowercaseListProperty;
@@ -74,11 +76,15 @@ public class SecuritySettings implements SettingsHolder {
7476
public static final Property<Integer> DOUBLE_MD5_SALT_LENGTH =
7577
newProperty("settings.security.doubleMD5SaltLength", 8);
7678

77-
@Comment({"If password checking return false, do we need to check with all",
78-
"other password algorithm to check an old password?",
79-
"AuthMe will update the password to the new password hash"})
80-
public static final Property<Boolean> SUPPORT_OLD_PASSWORD_HASH =
81-
newProperty("settings.security.supportOldPasswordHash", false);
79+
@Comment({
80+
"If a password check fails, AuthMe will also try to check with the following hash methods.",
81+
"Use this setting when you change from one hash method to another.",
82+
"AuthMe will update the password to the new hash. Example:",
83+
"legacyHashes:",
84+
"- 'SHA1'"
85+
})
86+
public static final Property<List<HashAlgorithm>> LEGACY_HASHES =
87+
new EnumSetProperty<>(HashAlgorithm.class, "settings.security.legacyHashes", Collections.emptyList());
8288

8389
@Comment({"Prevent unsafe passwords from being used; put them in lowercase!",
8490
"You should always set 'help' as unsafePassword due to possible conflicts.",

src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java

+20-15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
import org.mockito.runners.MockitoJUnitRunner;
2525
import org.mockito.stubbing.Answer;
2626

27+
import java.util.Collections;
28+
import java.util.List;
29+
30+
import static com.google.common.collect.Lists.newArrayList;
2731
import static org.hamcrest.Matchers.equalTo;
2832
import static org.hamcrest.Matchers.equalToIgnoringCase;
2933
import static org.junit.Assert.assertThat;
@@ -97,7 +101,7 @@ public void shouldReturnPasswordMatch() {
97101

98102
given(dataSource.getPassword(playerName)).willReturn(password);
99103
given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(true);
100-
initSettings(HashAlgorithm.BCRYPT, false);
104+
initSettings(HashAlgorithm.BCRYPT);
101105
PasswordSecurity security = newPasswordSecurity();
102106

103107
// when
@@ -120,7 +124,7 @@ public void shouldReturnPasswordMismatch() {
120124

121125
given(dataSource.getPassword(playerName)).willReturn(password);
122126
given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false);
123-
initSettings(HashAlgorithm.CUSTOM, false);
127+
initSettings(HashAlgorithm.CUSTOM);
124128
PasswordSecurity security = newPasswordSecurity();
125129

126130
// when
@@ -140,7 +144,7 @@ public void shouldReturnFalseIfPlayerDoesNotExist() {
140144
String clearTextPass = "tables";
141145

142146
given(dataSource.getPassword(playerName)).willReturn(null);
143-
initSettings(HashAlgorithm.MD5, false);
147+
initSettings(HashAlgorithm.MD5);
144148
PasswordSecurity security = newPasswordSecurity();
145149

146150
// when
@@ -168,7 +172,8 @@ public void shouldTryOtherMethodsForFailedPassword() {
168172
given(dataSource.getPassword(argThat(equalToIgnoringCase(playerName)))).willReturn(password);
169173
given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false);
170174
given(method.computeHash(clearTextPass, playerLowerCase)).willReturn(newPassword);
171-
initSettings(HashAlgorithm.MD5, true);
175+
initSettings(HashAlgorithm.MD5);
176+
given(settings.getProperty(SecuritySettings.LEGACY_HASHES)).willReturn(newArrayList(HashAlgorithm.BCRYPT));
172177
PasswordSecurity security = newPasswordSecurity();
173178

174179
// when
@@ -193,7 +198,7 @@ public void shouldTryAllMethodsAndFail() {
193198
String clearTextPass = "someInvalidPassword";
194199
given(dataSource.getPassword(playerName)).willReturn(password);
195200
given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false);
196-
initSettings(HashAlgorithm.MD5, true);
201+
initSettings(HashAlgorithm.MD5);
197202
PasswordSecurity security = newPasswordSecurity();
198203

199204
// when
@@ -212,7 +217,7 @@ public void shouldHashPassword() {
212217
String usernameLowerCase = username.toLowerCase();
213218
HashedPassword hashedPassword = new HashedPassword("$T$est#Hash", "__someSalt__");
214219
given(method.computeHash(password, usernameLowerCase)).willReturn(hashedPassword);
215-
initSettings(HashAlgorithm.JOOMLA, true);
220+
initSettings(HashAlgorithm.JOOMLA);
216221
PasswordSecurity security = newPasswordSecurity();
217222

218223
// when
@@ -234,7 +239,7 @@ public void shouldSkipCheckIfMandatorySaltIsUnavailable() {
234239
String username = "someone12";
235240
HashedPassword hashedPassword = new HashedPassword("~T!est#Hash");
236241
given(method.hasSeparateSalt()).willReturn(true);
237-
initSettings(HashAlgorithm.XAUTH, false);
242+
initSettings(HashAlgorithm.XAUTH);
238243
PasswordSecurity security = newPasswordSecurity();
239244

240245
// when
@@ -250,19 +255,20 @@ public void shouldSkipCheckIfMandatorySaltIsUnavailable() {
250255
@Test
251256
public void shouldReloadSettings() {
252257
// given
253-
initSettings(HashAlgorithm.BCRYPT, false);
258+
initSettings(HashAlgorithm.BCRYPT);
254259
PasswordSecurity passwordSecurity = newPasswordSecurity();
255260
given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.MD5);
256-
given(settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH)).willReturn(true);
261+
List<HashAlgorithm> legacyHashes = newArrayList(HashAlgorithm.CUSTOM, HashAlgorithm.BCRYPT);
262+
given(settings.getProperty(SecuritySettings.LEGACY_HASHES)).willReturn(legacyHashes);
257263

258264
// when
259265
passwordSecurity.reload();
260266

261267
// then
262268
assertThat(ReflectionTestUtils.getFieldValue(PasswordSecurity.class, passwordSecurity, "algorithm"),
263-
equalTo((Object) HashAlgorithm.MD5));
264-
assertThat(ReflectionTestUtils.getFieldValue(PasswordSecurity.class, passwordSecurity, "supportOldAlgorithm"),
265-
equalTo((Object) Boolean.TRUE));
269+
equalTo(HashAlgorithm.MD5));
270+
assertThat(ReflectionTestUtils.getFieldValue(PasswordSecurity.class, passwordSecurity, "legacyAlgorithms"),
271+
equalTo(legacyHashes));
266272
}
267273

268274
private PasswordSecurity newPasswordSecurity() {
@@ -275,11 +281,10 @@ private PasswordSecurity newPasswordSecurity() {
275281
return passwordSecurity;
276282
}
277283

278-
private void initSettings(HashAlgorithm algorithm, boolean supportOldPassword) {
284+
private void initSettings(HashAlgorithm algorithm) {
279285
given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(algorithm);
280-
given(settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH)).willReturn(supportOldPassword);
286+
given(settings.getProperty(SecuritySettings.LEGACY_HASHES)).willReturn(Collections.emptyList());
281287
given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8);
282-
given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(16);
283288
}
284289

285290
}

0 commit comments

Comments
 (0)