#1016 Deprecate MD5, SHA1, SHA512: no longer allowed for active use

- Deprecate unsalted hashes: if such a hash is configured, move it to the legacy hashes setting to still support the existing hashes in the database but hash all passwords from now on with our default, SHA256.
This commit is contained in:
ljacqu 2017-10-19 21:30:19 +02:00
parent 7d445217d6
commit fca77b940f
14 changed files with 121 additions and 87 deletions

View File

@ -23,7 +23,6 @@ import fr.xephi.authme.listener.PlayerListener18;
import fr.xephi.authme.listener.PlayerListener19;
import fr.xephi.authme.listener.PlayerListener19Spigot;
import fr.xephi.authme.listener.ServerListener;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.Sha256;
import fr.xephi.authme.service.BackupService;
import fr.xephi.authme.service.BukkitService;
@ -268,13 +267,6 @@ public class AuthMe extends JavaPlugin {
&& settings.getProperty(EmailSettings.SMTP_PORT) != 25) {
ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25");
}
// Unsalted hashes will be deprecated in 5.4 (see Github issue #1016)
HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH);
if (OnStartupTasks.isHashDeprecatedIn54(hash)) {
ConsoleLogger.warning("You are using an unsalted hash (" + hash + "). Support for this will be removed "
+ "in 5.4 -- do you still need it? Comment on https://github.com/AuthMe/AuthMeReloaded/issues/1016");
}
}
/**

View File

@ -7,9 +7,6 @@ import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.output.ConsoleFilter;
import fr.xephi.authme.output.Log4JFilter;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
@ -103,23 +100,4 @@ public class OnStartupTasks {
}
}, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL));
}
/**
* Returns whether the hash algorithm is deprecated and won't be able
* to be actively used anymore in 5.4.
*
* @param hash the hash algorithm to check
* @return true if the hash will be deprecated, false otherwise
* @see <a href="https://github.com/AuthMe/AuthMeReloaded/issues/1016">#1016</a>
*/
public static boolean isHashDeprecatedIn54(HashAlgorithm hash) {
if (hash.getClazz() == null || hash == HashAlgorithm.PLAINTEXT) {
// Exclude PLAINTEXT from this check because it already has a mandatory migration, which takes care of
// sending all the necessary messages and warnings.
return false;
}
Recommendation recommendation = hash.getClazz().getAnnotation(Recommendation.class);
return recommendation != null && recommendation.value() == Usage.DEPRECATED;
}
}

View File

@ -10,34 +10,34 @@ public enum HashAlgorithm {
BCRYPT(fr.xephi.authme.security.crypts.BCrypt.class),
BCRYPT2Y(fr.xephi.authme.security.crypts.BCrypt2y.class),
CRAZYCRYPT1(fr.xephi.authme.security.crypts.CrazyCrypt1.class),
DOUBLEMD5(fr.xephi.authme.security.crypts.DoubleMd5.class),
IPB3(fr.xephi.authme.security.crypts.Ipb3.class),
IPB4(fr.xephi.authme.security.crypts.Ipb4.class),
JOOMLA(fr.xephi.authme.security.crypts.Joomla.class),
MD5(fr.xephi.authme.security.crypts.Md5.class),
MD5VB(fr.xephi.authme.security.crypts.Md5vB.class),
MYBB(fr.xephi.authme.security.crypts.MyBB.class),
PBKDF2(fr.xephi.authme.security.crypts.Pbkdf2.class),
PBKDF2DJANGO(fr.xephi.authme.security.crypts.Pbkdf2Django.class),
PHPBB(fr.xephi.authme.security.crypts.PhpBB.class),
PHPFUSION(fr.xephi.authme.security.crypts.PhpFusion.class),
@Deprecated
PLAINTEXT(fr.xephi.authme.security.crypts.PlainText.class),
ROYALAUTH(fr.xephi.authme.security.crypts.RoyalAuth.class),
SALTED2MD5(fr.xephi.authme.security.crypts.Salted2Md5.class),
SALTEDSHA512(fr.xephi.authme.security.crypts.SaltedSha512.class),
SHA1(fr.xephi.authme.security.crypts.Sha1.class),
SHA256(fr.xephi.authme.security.crypts.Sha256.class),
SHA512(fr.xephi.authme.security.crypts.Sha512.class),
SMF(fr.xephi.authme.security.crypts.Smf.class),
TWO_FACTOR(fr.xephi.authme.security.crypts.TwoFactor.class),
WBB3(fr.xephi.authme.security.crypts.Wbb3.class),
WBB4(fr.xephi.authme.security.crypts.Wbb4.class),
WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class),
WORDPRESS(fr.xephi.authme.security.crypts.Wordpress.class),
XAUTH(fr.xephi.authme.security.crypts.XAuth.class),
XFBCRYPT(fr.xephi.authme.security.crypts.XfBCrypt.class),
CUSTOM(null);
CUSTOM(null),
@Deprecated DOUBLEMD5(fr.xephi.authme.security.crypts.DoubleMd5.class),
@Deprecated MD5(fr.xephi.authme.security.crypts.Md5.class),
@Deprecated PLAINTEXT(fr.xephi.authme.security.crypts.PlainText.class),
@Deprecated SHA1(fr.xephi.authme.security.crypts.Sha1.class),
@Deprecated SHA512(fr.xephi.authme.security.crypts.Sha512.class),
@Deprecated WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class);
private final Class<? extends EncryptionMethod> clazz;

View File

@ -5,6 +5,7 @@ import fr.xephi.authme.security.crypts.description.Usage;
import static fr.xephi.authme.security.HashUtils.md5;
@Deprecated
@Recommendation(Usage.DEPRECATED)
public class DoubleMd5 extends UnsaltedMethod {

View File

@ -4,6 +4,7 @@ import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
@Deprecated
@Recommendation(Usage.DEPRECATED)
public class Md5 extends UnsaltedMethod {

View File

@ -4,6 +4,7 @@ import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
@Deprecated
@Recommendation(Usage.DEPRECATED)
public class Sha1 extends UnsaltedMethod {

View File

@ -4,6 +4,7 @@ import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
@Deprecated
@Recommendation(Usage.DEPRECATED)
public class Sha512 extends UnsaltedMethod {

View File

@ -64,6 +64,7 @@ import fr.xephi.authme.security.crypts.description.Usage;
import java.util.Arrays;
@Deprecated
@Recommendation(Usage.DEPRECATED)
public class Whirlpool extends UnsaltedMethod {

View File

@ -3,8 +3,8 @@ package fr.xephi.authme.settings;
import ch.jalu.configme.properties.Property;
import ch.jalu.configme.resource.PropertyResource;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -27,9 +27,9 @@ public class EnumSetProperty<E extends Enum<E>> extends Property<Set<E>> {
@Override
protected Set<E> getFromResource(PropertyResource resource) {
List<?> elements = resource.getList(getPath());
if (elements != null) {
return elements.stream()
Object entry = resource.getObject(getPath());
if (entry instanceof Collection<?>) {
return ((Collection<?>) entry).stream()
.map(val -> toEnum(String.valueOf(val)))
.filter(e -> e != null)
.collect(Collectors.toCollection(LinkedHashSet::new));

View File

@ -10,6 +10,8 @@ import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
@ -20,6 +22,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
@ -73,6 +76,7 @@ public class SettingsMigrationService extends PlainMigrationService {
| hasSupportOldPasswordProperty(resource)
| convertToRegistrationType(resource)
| mergeAndMovePermissionGroupSettings(resource)
| moveDeprecatedHashAlgorithmIntoLegacySection(resource)
|| hasDeprecatedProperties(resource);
}
@ -286,6 +290,31 @@ public class SettingsMigrationService extends PlainMigrationService {
return performedChanges;
}
/**
* If a deprecated hash is used, it is added to the legacy hashes option and the active hash
* is changed to SHA256.
*
* @param resource The property resource
* @return True if the configuration has changed, false otherwise
*/
private static boolean moveDeprecatedHashAlgorithmIntoLegacySection(PropertyResource resource) {
HashAlgorithm currentHash = SecuritySettings.PASSWORD_HASH.getValue(resource);
// Skip CUSTOM (has no class) and PLAINTEXT (is force-migrated later on in the startup process)
if (currentHash != HashAlgorithm.CUSTOM && currentHash != HashAlgorithm.PLAINTEXT) {
Class<? extends EncryptionMethod> clazz = currentHash.getClazz();
if (clazz.isAnnotationPresent(Deprecated.class)) {
resource.setValue(SecuritySettings.PASSWORD_HASH.getPath(), HashAlgorithm.SHA256);
Set<HashAlgorithm> legacyHashes = SecuritySettings.LEGACY_HASHES.getValue(resource);
legacyHashes.add(currentHash);
resource.setValue(SecuritySettings.LEGACY_HASHES.getPath(), legacyHashes);
ConsoleLogger.warning("The hash algorithm '" + currentHash
+ "' is no longer supported for active use. New hashes will be in SHA256.");
return true;
}
}
return false;
}
/**
* Checks for an old property path and moves it to a new path if it is present and the new path is not yet set.
*

View File

@ -53,7 +53,7 @@ public final class SecuritySettings implements SettingsHolder {
newProperty("settings.security.passwordMaxLength", 30);
@Comment({
"Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,",
"Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512,",
"MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,",
"PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at",
"https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/hash_algorithms.md"

View File

@ -1,27 +0,0 @@
package fr.xephi.authme.initialization;
import fr.xephi.authme.security.HashAlgorithm;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Test for {@link OnStartupTasks}.
*/
public class OnStartupTasksTest {
@Test
public void shouldCheckIfHashIsDeprecatedIn54() {
// given / when / then
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.CUSTOM), equalTo(false));
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.IPB3), equalTo(false));
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.PLAINTEXT), equalTo(false));
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.SHA256), equalTo(false));
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.WORDPRESS), equalTo(false));
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.MD5), equalTo(true));
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.SHA512), equalTo(true));
assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.WHIRLPOOL), equalTo(true));
}
}

View File

@ -1,12 +1,16 @@
package fr.xephi.authme.settings;
import ch.jalu.configme.configurationdata.ConfigurationData;
import ch.jalu.configme.properties.Property;
import ch.jalu.configme.resource.PropertyResource;
import ch.jalu.configme.resource.YamlFileResource;
import com.google.common.io.Files;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever;
import org.junit.BeforeClass;
import org.junit.Rule;
@ -16,6 +20,8 @@ import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static fr.xephi.authme.TestHelper.getJarFile;
import static fr.xephi.authme.settings.properties.PluginSettings.ENABLE_PERMISSION_CHECK;
@ -28,6 +34,8 @@ import static fr.xephi.authme.settings.properties.RegistrationSettings.REGISTRAT
import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS;
import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN;
import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_ON_WORLDS;
import static fr.xephi.authme.settings.properties.SecuritySettings.LEGACY_HASHES;
import static fr.xephi.authme.settings.properties.SecuritySettings.PASSWORD_HASH;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@ -47,8 +55,9 @@ public class SettingsMigrationServiceTest {
TestHelper.setupLogger();
}
/* When settings are loaded, test that migrations are applied and immediately available in memory. */
@Test
public void shouldPerformMigrations() throws IOException {
public void shouldPerformMigrationsInMemory() throws IOException {
// given
File dataFolder = temporaryFolder.newFolder();
File configFile = new File(dataFolder, "config.yml");
@ -61,22 +70,31 @@ public class SettingsMigrationServiceTest {
dataFolder, resource, migrationService, AuthMeSettingsRetriever.buildConfigurationData());
// then
assertThat(settings.getProperty(ALLOWED_NICKNAME_CHARACTERS), equalTo(ALLOWED_NICKNAME_CHARACTERS.getDefaultValue()));
assertThat(settings.getProperty(DELAY_JOIN_MESSAGE), equalTo(true));
assertThat(settings.getProperty(FORCE_SPAWN_LOCATION_AFTER_LOGIN), equalTo(true));
assertThat(settings.getProperty(FORCE_SPAWN_ON_WORLDS), contains("survival", "survival_nether", "creative"));
assertThat(settings.getProperty(LOG_LEVEL), equalTo(LogLevel.INFO));
assertThat(settings.getProperty(REGISTRATION_TYPE), equalTo(RegistrationType.EMAIL));
assertThat(settings.getProperty(REGISTER_SECOND_ARGUMENT), equalTo(RegisterSecondaryArgument.CONFIRMATION));
assertThat(settings.getProperty(ENABLE_PERMISSION_CHECK), equalTo(true));
assertThat(settings.getProperty(REGISTERED_GROUP), equalTo("unLoggedinGroup"));
assertThat(settings.getProperty(UNREGISTERED_GROUP), equalTo(""));
verifyHasUpToDateSettings(settings, dataFolder);
}
// Check migration of old setting to email.html
assertThat(Files.readLines(new File(dataFolder, "email.html"), StandardCharsets.UTF_8),
contains("Dear <playername />, <br /><br /> This is your new AuthMe password for the server "
+ "<br /><br /> <servername /> : <br /><br /> <generatedpass /><br /><image /><br />Do not forget to "
+ "change password after login! <br /> /changepassword <generatedpass /> newPassword"));
/*
* When settings are loaded, test that migrations are applied and persisted to disk,
* i.e. when the settings are loaded again from the file, no migrations should be necessary.
*/
@Test
public void shouldPerformMigrationsAndPersistToDisk() throws IOException {
// given
File dataFolder = temporaryFolder.newFolder();
File configFile = new File(dataFolder, "config.yml");
Files.copy(getJarFile(OLD_CONFIG_FILE), configFile);
PropertyResource resource = new YamlFileResource(configFile);
TestMigrationServiceExtension migrationService = new TestMigrationServiceExtension(dataFolder);
ConfigurationData configurationData = AuthMeSettingsRetriever.buildConfigurationData();
// when
new Settings(dataFolder, resource, migrationService, configurationData);
resource = new YamlFileResource(configFile);
Settings settings = new Settings(dataFolder, resource, migrationService, configurationData);
// then
verifyHasUpToDateSettings(settings, dataFolder);
assertThat(migrationService.returnedValues, contains(true, false));
}
@Test
@ -97,4 +115,40 @@ public class SettingsMigrationServiceTest {
assertThat(migrationService.getOnRegisterCommands(), contains("me registers", "msg CONSOLE hi"));
assertThat(migrationService.getOnRegisterConsoleCommands(), contains("sethome %p:regloc"));
}
private void verifyHasUpToDateSettings(Settings settings, File dataFolder) throws IOException {
assertThat(settings.getProperty(ALLOWED_NICKNAME_CHARACTERS), equalTo(ALLOWED_NICKNAME_CHARACTERS.getDefaultValue()));
assertThat(settings.getProperty(DELAY_JOIN_MESSAGE), equalTo(true));
assertThat(settings.getProperty(FORCE_SPAWN_LOCATION_AFTER_LOGIN), equalTo(true));
assertThat(settings.getProperty(FORCE_SPAWN_ON_WORLDS), contains("survival", "survival_nether", "creative"));
assertThat(settings.getProperty(LOG_LEVEL), equalTo(LogLevel.INFO));
assertThat(settings.getProperty(REGISTRATION_TYPE), equalTo(RegistrationType.EMAIL));
assertThat(settings.getProperty(REGISTER_SECOND_ARGUMENT), equalTo(RegisterSecondaryArgument.CONFIRMATION));
assertThat(settings.getProperty(ENABLE_PERMISSION_CHECK), equalTo(true));
assertThat(settings.getProperty(REGISTERED_GROUP), equalTo("unLoggedinGroup"));
assertThat(settings.getProperty(UNREGISTERED_GROUP), equalTo(""));
assertThat(settings.getProperty(PASSWORD_HASH), equalTo(HashAlgorithm.SHA256));
assertThat(settings.getProperty(LEGACY_HASHES), contains(HashAlgorithm.PBKDF2, HashAlgorithm.WORDPRESS, HashAlgorithm.SHA512));
// Check migration of old setting to email.html
assertThat(Files.readLines(new File(dataFolder, "email.html"), StandardCharsets.UTF_8),
contains("Dear <playername />, <br /><br /> This is your new AuthMe password for the server "
+ "<br /><br /> <servername /> : <br /><br /> <generatedpass /><br /><image /><br />Do not forget to "
+ "change password after login! <br /> /changepassword <generatedpass /> newPassword"));
}
private static class TestMigrationServiceExtension extends SettingsMigrationService {
private List<Boolean> returnedValues = new ArrayList<>();
TestMigrationServiceExtension(@DataFolder File pluginFolder) {
super(pluginFolder);
}
@Override
protected boolean performMigrations(PropertyResource resource, List<Property<?>> properties) {
boolean result = super.performMigrations(resource, properties);
returnedValues.add(result);
return result;
}
}
}

View File

@ -191,7 +191,10 @@ settings:
# PLAINTEXT ( unhashed password),
# MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512,
# DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM(for developpers only)
passwordHash: SHA256
passwordHash: SHA512
legacyHashes:
- PBKDF2
- WORDPRESS
# salt length for the SALTED2MD5 MD5(MD5(password)+salt)
doubleMD5SaltLength: 8
# If password checking return false , do we need to check with all