The XML doc on AccountSecurity.RepairMigratedPasswords still described the superseded single-switch design: it presented the flag as the only control and never mentioned Account.RepairPasswordTag, so an operator reading only that comment would enable the flag, watch nothing get repaired, and conclude the feature was broken. Its cost rationale was overstated too -- the second verify now runs only for tagged accounts, not on every failed login, so the flag alone does not widen the credential-stuffing surface. Rewrite it to say what the code does: master switch, tag independently required, set from the admin gump and cleared on a successful repair, and only ever on an account whose owner has reported the lockout -- because the repair cannot tell a mis-migrated hash from a password that merely begins with the username. Cross-reference the two by cref in both directions so either comment leads to the other. Comment-only; no behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
87 lines
4.2 KiB
C#
87 lines
4.2 KiB
C#
/*************************************************************************
|
|
* ModernUO *
|
|
* Copyright 2019-2026 - ModernUO Development Team *
|
|
* Email: hi@modernuo.com *
|
|
* File: AccountSecurity.cs *
|
|
* *
|
|
* This program is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation, either version 3 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
*************************************************************************/
|
|
|
|
using System;
|
|
|
|
namespace Server.Accounting.Security;
|
|
|
|
public enum PasswordProtectionAlgorithm
|
|
{
|
|
// Obsolete algorithms from RunUO. These are not secure!
|
|
// They are included for password upgrades only.
|
|
None,
|
|
MD5,
|
|
SHA1,
|
|
|
|
// Supported algorithms
|
|
SHA2, // ServUO compatibility
|
|
PBKDF2,
|
|
Argon2 // Recommended algorithm for real security.
|
|
}
|
|
|
|
public static class AccountSecurity
|
|
{
|
|
public static PasswordProtectionAlgorithm CurrentAlgorithm { get; set; }
|
|
|
|
/// <summary>
|
|
/// Shard-wide master switch for the one-time repair of accounts whose password was corrupted by
|
|
/// the pre-fix SetPassword, which stored the credential under the wrong family's phrase rule.
|
|
/// This flag alone repairs nothing: an account is only repaired when it *also* carries the
|
|
/// <see cref="Account.RepairPasswordTag"/> tag, which an operator adds from the admin gump
|
|
/// (Account Details -> Tags -> Add Tag) and which is cleared automatically once the repair
|
|
/// succeeds. Tag only an account whose owner has actually reported being locked out, never one
|
|
/// that can still log in: the repair cannot distinguish a mis-migrated hash from a password that
|
|
/// merely begins with the username, so tagging a working account risks rewriting its credential
|
|
/// down to whatever was submitted. Off by default because the repair costs a second verify on a
|
|
/// failed login -- for tagged accounts only, so the credential-stuffing surface is unaffected by
|
|
/// the flag on its own. Turn it on for a migration window, then off again.
|
|
/// </summary>
|
|
public static bool RepairMigratedPasswords { get; set; }
|
|
|
|
public static IPasswordProtection CurrentPasswordProtection => GetPasswordProtection(CurrentAlgorithm);
|
|
|
|
public static void Configure()
|
|
{
|
|
CurrentAlgorithm =
|
|
ServerConfiguration.GetOrUpdateSetting(
|
|
"accountSecurity.encryptionAlgorithm",
|
|
PasswordProtectionAlgorithm.Argon2
|
|
);
|
|
|
|
RepairMigratedPasswords =
|
|
ServerConfiguration.GetOrUpdateSetting("accountSecurity.repairMigratedPasswords", false);
|
|
|
|
if (CurrentAlgorithm < PasswordProtectionAlgorithm.SHA2)
|
|
{
|
|
throw new Exception($"Security: {CurrentAlgorithm} is obsolete and not secure. Do not use it.");
|
|
}
|
|
}
|
|
|
|
public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm)
|
|
{
|
|
var passwordProtection = algorithm switch
|
|
{
|
|
PasswordProtectionAlgorithm.MD5 => HashAlgorithmPasswordProtection.MD5Instance,
|
|
PasswordProtectionAlgorithm.SHA1 => HashAlgorithmPasswordProtection.SHA1Instance,
|
|
PasswordProtectionAlgorithm.SHA2 => HashAlgorithmPasswordProtection.SHA2Instance,
|
|
PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance,
|
|
PasswordProtectionAlgorithm.Argon2 => Argon2PasswordProtection.Instance,
|
|
PasswordProtectionAlgorithm.None => throw new Exception("Do not use PasswordProtectionAlgorithm.None"),
|
|
_ => throw new Exception("No algorithm")
|
|
};
|
|
|
|
return passwordProtection;
|
|
}
|
|
}
|