Changed the settings getting functions to accept a fll key path instead of only a Section / Key pair. Changed the placement of the pepper in the password hashing function to be in complicance with the OWASP's recommendation for using a pwpper.
This commit is contained in:
Binary file not shown.
@@ -27,13 +27,13 @@ namespace SecureCore
|
|||||||
catch { return false; }
|
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;
|
setting = string.Empty;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var token = Settings.SelectToken($"{sectionName}.{key}");
|
var token = Settings.SelectToken(keyPath);
|
||||||
|
|
||||||
if (token == null) return false;
|
if (token == null) return false;
|
||||||
|
|
||||||
@@ -45,13 +45,13 @@ namespace SecureCore
|
|||||||
catch { return false; }
|
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;
|
setting = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var token = Settings.SelectToken($"{sectionName}.{key}");
|
var token = Settings.SelectToken(keyPath);
|
||||||
|
|
||||||
if (token == null) return false;
|
if (token == null) return false;
|
||||||
|
|
||||||
|
|||||||
@@ -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 (!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
|
//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
|
//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;
|
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
|
//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
|
//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;
|
MinPasswordLength = minPasswordLength;
|
||||||
MaxPasswordLength = maxPasswordLength;
|
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.
|
//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.
|
//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.");
|
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;
|
Pepper = pepper;
|
||||||
//Finally, the work factor (A.K.A. iterations) for the hashing algorithm.
|
//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
|
//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
|
//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)
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ namespace SecureCore.Controllers
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class AuthController : Controller
|
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.".
|
//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")]
|
[HttpPost("login")]
|
||||||
[AcceptVerbs("POST")]
|
[AcceptVerbs("POST")]
|
||||||
@@ -167,9 +172,11 @@ namespace SecureCore.Controllers
|
|||||||
{
|
{
|
||||||
PasswordManager.InsertPasswordResetRequest(email, token, DateTime.Now.AddHours(1), agent, ip, connectionString);
|
PasswordManager.InsertPasswordResetRequest(email, token, DateTime.Now.AddHours(1), agent, ip, connectionString);
|
||||||
|
|
||||||
|
AppSettingsManager.TryGetSettingString("PasswordSettings.BaseURL", out string baseAddress);
|
||||||
|
|
||||||
token = HttpUtility.UrlEncode(token);
|
token = HttpUtility.UrlEncode(token);
|
||||||
//TODO: Email the link to the supplied email.
|
//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)
|
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
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
private CookieOptions GetCookieOptions()
|
private CookieOptions GetCookieOptions()
|
||||||
{
|
{
|
||||||
return new CookieOptions
|
return new CookieOptions
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"PasswordSettings": {
|
"PasswordSettings": {
|
||||||
"Pepper": "rVk/OwQUw01qy76Q+5WimPk+NdqUMMghftMXyJzzckOj/+eFn056PDYzBD61E/ZNjRdgiMK6RhcHEcdfpJdbcw==",
|
"Pepper": "rVk/OwQUw01qy76Q+5WimPk+NdqUMMghftMXyJzzckOj/+eFn056PDYzBD61E/ZNjRdgiMK6RhcHEcdfpJdbcw==",
|
||||||
"MaxLength": 128,
|
"MaxLength": 128,
|
||||||
"MinLength": 22
|
"MinLength": 22,
|
||||||
|
"BaseURL": "localhost:3000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user