diff --git a/.vs/SecureCore/v16/.suo b/.vs/SecureCore/v16/.suo index db49d7e..23974ff 100644 Binary files a/.vs/SecureCore/v16/.suo and b/.vs/SecureCore/v16/.suo differ diff --git a/SecureCore/AppSettingsManager.cs b/SecureCore/AppSettingsManager.cs index 10c6a96..3e17b4a 100644 --- a/SecureCore/AppSettingsManager.cs +++ b/SecureCore/AppSettingsManager.cs @@ -27,13 +27,13 @@ namespace SecureCore catch { return false; } } - public static bool TryGetSettingString(string sectionName, string key, out string setting) + public static bool TryGetSettingString(string keyPath, out string setting) { setting = string.Empty; try { - var token = Settings.SelectToken($"{sectionName}.{key}"); + var token = Settings.SelectToken(keyPath); if (token == null) return false; @@ -45,13 +45,13 @@ namespace SecureCore catch { return false; } } - public static bool TryGetSettingInt(string sectionName, string key, out int setting) + public static bool TryGetSettingInt(string keyPath, out int setting) { setting = 0; try { - var token = Settings.SelectToken($"{sectionName}.{key}"); + var token = Settings.SelectToken(keyPath); if (token == null) return false; diff --git a/SecureCore/Authentication/PasswordManager.cs b/SecureCore/Authentication/PasswordManager.cs index 22578f6..c7e0d5e 100644 --- a/SecureCore/Authentication/PasswordManager.cs +++ b/SecureCore/Authentication/PasswordManager.cs @@ -26,7 +26,7 @@ namespace SecureCore.Authentication { if (!string.IsNullOrEmpty(Pepper)) throw new InvalidOperationException("The PasswordManager's settings have already been initialized. Operation aborted."); - if (AppSettingsManager.TryGetSettingInt(SectionName, "MaxLength", out int maxPasswordLength)) + if (AppSettingsManager.TryGetSettingInt($"{SectionName}.MaxLength", out int maxPasswordLength)) { //As noted in this article https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#maximum-password-lengths //allowing passwords that are too long can result in a denial-of-service attack. So we must enforce an upper bound @@ -36,7 +36,7 @@ namespace SecureCore.Authentication } else maxPasswordLength = AbsoluteMaxPasswordLength; - if (AppSettingsManager.TryGetSettingInt(SectionName, "MinLength", out int minPasswordLength)) + if (AppSettingsManager.TryGetSettingInt($"{SectionName}.MinLength", out int minPasswordLength)) { //There was no mention of a min password length in the above article, so I've chosen on a whim that 16 should //be a safe enough min on a password's length. So as usual, just ignore settings that are out of bounds and @@ -54,7 +54,7 @@ namespace SecureCore.Authentication MinPasswordLength = minPasswordLength; MaxPasswordLength = maxPasswordLength; //Now read in the pepper. A pepper being a string of characters at least 32 characters long that is NOT stored in the database and is used in conjunction with hashing sensitive user data. - if (AppSettingsManager.TryGetSettingString(SectionName, "Pepper", out string pepper)) + if (AppSettingsManager.TryGetSettingString($"{SectionName}.Pepper", out string pepper)) { //As noted here https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html a pepper should be at least 32 bytes in size. if (pepper.Length < AbsoluteMinPepperLength) throw new Exception("A pepper must be at least 32 characters long for security reasons."); @@ -64,7 +64,7 @@ namespace SecureCore.Authentication Pepper = pepper; //Finally, the work factor (A.K.A. iterations) for the hashing algorithm. - if (AppSettingsManager.TryGetSettingInt(SectionName, "Iterations", out int iterations)) + if (AppSettingsManager.TryGetSettingInt($"{SectionName}.Iterations", out int iterations)) { //The work factor must be of a certain strength and if it fails this check then we will be forced to ignore it and use the recommended work factor //as stated here: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 @@ -171,7 +171,7 @@ namespace SecureCore.Authentication private static string GetHash(string password, byte[] salt) { - return Convert.ToBase64String(KeyDerivation.Pbkdf2($"{password}{Pepper}", salt, KeyType, Iterations, KeySize)); + return Convert.ToBase64String(KeyDerivation.Pbkdf2($"{Pepper}{password}", salt, KeyType, Iterations, KeySize)); } } } diff --git a/SecureCore/Controllers/AuthController.cs b/SecureCore/Controllers/AuthController.cs index 173bcb4..6ff3bf8 100644 --- a/SecureCore/Controllers/AuthController.cs +++ b/SecureCore/Controllers/AuthController.cs @@ -12,7 +12,12 @@ namespace SecureCore.Controllers [ApiController] public class AuthController : Controller { - public static string BaseUrl { get; set; } + [HttpPost("IsLoggedIn")] + [AcceptVerbs("POST")] + public IActionResult IsLoggedIn() + { + return Ok(); + } //TODO: Login will only ever return messages like "Wrong username / password." whereas register can return messages like "User exists.", "Password to weak", or "Password in top 100 most used.". [HttpPost("login")] [AcceptVerbs("POST")] @@ -167,9 +172,11 @@ namespace SecureCore.Controllers { PasswordManager.InsertPasswordResetRequest(email, token, DateTime.Now.AddHours(1), agent, ip, connectionString); + AppSettingsManager.TryGetSettingString("PasswordSettings.BaseURL", out string baseAddress); + token = HttpUtility.UrlEncode(token); //TODO: Email the link to the supplied email. - return Ok($"192.168.255.200:5000/auth/ResetPassword?token={token}{Environment.NewLine}"); + return Ok($"{baseAddress}/ResetPassword?token={token}{Environment.NewLine}"); } catch(Exception e) { @@ -177,6 +184,11 @@ namespace SecureCore.Controllers } } + //TODO: Review https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-5.0 and https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html + /// + /// + /// + /// private CookieOptions GetCookieOptions() { return new CookieOptions diff --git a/SecureCore/appsettings.json b/SecureCore/appsettings.json index ab529bb..d55f02e 100644 --- a/SecureCore/appsettings.json +++ b/SecureCore/appsettings.json @@ -16,6 +16,7 @@ "PasswordSettings": { "Pepper": "rVk/OwQUw01qy76Q+5WimPk+NdqUMMghftMXyJzzckOj/+eFn056PDYzBD61E/ZNjRdgiMK6RhcHEcdfpJdbcw==", "MaxLength": 128, - "MinLength": 22 + "MinLength": 22, + "BaseURL": "localhost:3000" } }