Sprint 8 #43

Merged
csimonapastore merged 20 commits from sprints/8 into main 2025-06-21 01:11:03 +02:00
9 changed files with 50 additions and 45 deletions
Showing only changes of commit 1877c29e68 - Show all commits

View File

@@ -35,8 +35,9 @@
"ExpiredAfterMinsOfInactivity": 15 "ExpiredAfterMinsOfInactivity": 15
}, },
"EncryptionSettings": { "EncryptionSettings": {
"Salt": "S7VIidfXQf1tOQYX", "Secret": "S7VIidfXQf1tOQYX",
"Pepper": "" "Salt": "",
"Iterations": 10
} }
} }

View File

@@ -35,8 +35,9 @@
"ExpiredAfterMinsOfInactivity": 15 "ExpiredAfterMinsOfInactivity": 15
}, },
"EncryptionSettings": { "EncryptionSettings": {
"Salt": "AAAAA", "Secret": "AAAAA",
"Pepper": "" "Salt": "",
"Iterations": 10
} }
} }

View File

@@ -13,8 +13,9 @@ public static class ModelsInit
FirstName = "FirstName", FirstName = "FirstName",
LastName = "LastName", LastName = "LastName",
Email = "test-new@email.it", Email = "test-new@email.it",
PasswordHash = "PasswordHash", PasswordPepper = "PasswordPepper",
PasswordSalt = "PasswordSalt", PasswordSalt = "PasswordSalt",
PasswordIterations = 0,
Password = "Password", Password = "Password",
Role = CreateRole(), Role = CreateRole(),
IsTestUser = true IsTestUser = true

View File

@@ -102,11 +102,11 @@ public class CryptoUtils_Tests
} }
[TestMethod] [TestMethod]
public void GenerateSalt() public void GeneratePepper()
{ {
try try
{ {
var salt = CryptUtils.GenerateSalt(); var salt = CryptUtils.GeneratePepper();
Assert.IsTrue(!String.IsNullOrEmpty(salt)); Assert.IsTrue(!String.IsNullOrEmpty(salt));
} }
catch (Exception ex) catch (Exception ex)
@@ -122,14 +122,14 @@ public class CryptoUtils_Tests
try try
{ {
var password = "P4ssw0rd@1!"; var password = "P4ssw0rd@1!";
var salt = CryptUtils.GenerateSalt(); var pepper = CryptUtils.GeneratePepper();
Assert.IsTrue(!String.IsNullOrEmpty(salt)); Assert.IsTrue(!String.IsNullOrEmpty(pepper));
WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty<string>()); WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty<string>());
AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData"); AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData");
CryptUtils cryptoUtils = new CryptUtils(appSettings); var salt = appSettings?.EncryptionSettings?.Salt ?? String.Empty;
var encryptedPassword = cryptoUtils.GeneratePassword(password, salt, 0); var encryptedPassword = CryptUtils.GeneratePassword(password, salt, 0, pepper);
Assert.IsTrue(password != encryptedPassword); Assert.AreNotEqual(encryptedPassword, password);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -147,10 +147,7 @@ public class CryptoUtils_Tests
var salt = "Afi7PQYgEL2sPbNyVzduvg=="; var salt = "Afi7PQYgEL2sPbNyVzduvg==";
var hashedPassword = "2lMeySZ9ciH1KtSg1Z7oSJRmJEjHMeDvdaNRcJcGutM="; var hashedPassword = "2lMeySZ9ciH1KtSg1Z7oSJRmJEjHMeDvdaNRcJcGutM=";
WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty<string>()); var verified = CryptUtils.VerifyPassword(hashedPassword, password, salt, 0);
AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData");
CryptUtils cryptoUtils = new CryptUtils(appSettings);
var verified = cryptoUtils.VerifyPassword(password, salt, 0, hashedPassword);
Assert.IsTrue(verified); Assert.IsTrue(verified);
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -13,7 +13,10 @@ public class User : Base
[MaxLength(200)] [MaxLength(200)]
public required string Email { get; set; } public required string Email { get; set; }
public required string PasswordSalt { get; set; } public required string PasswordSalt { get; set; }
public required string PasswordHash { get; set; } #nullable enable
public string? PasswordPepper { get; set; }
#nullable disable
public required int PasswordIterations { get; set; }
public required Role Role { get; set; } public required Role Role { get; set; }
public required bool IsTestUser { get; set; } public required bool IsTestUser { get; set; }

View File

@@ -3,7 +3,8 @@ namespace BasicDotnetTemplate.MainProject.Models.Settings;
public class EncryptionSettings public class EncryptionSettings
{ {
#nullable enable #nullable enable
public string? Secret { get; set; }
public string? Salt { get; set; } public string? Salt { get; set; }
public string? Pepper { get; set; } public int? Iterations { get; set; }
#nullable disable #nullable disable
} }

View File

@@ -4,6 +4,7 @@ using BasicDotnetTemplate.MainProject.Core.Database;
using BasicDotnetTemplate.MainProject.Models.Api.Common.Exceptions; using BasicDotnetTemplate.MainProject.Models.Api.Common.Exceptions;
using BasicDotnetTemplate.MainProject.Models.Api.Data.User; using BasicDotnetTemplate.MainProject.Models.Api.Data.User;
using BasicDotnetTemplate.MainProject.Models.Database.SqlServer; using BasicDotnetTemplate.MainProject.Models.Database.SqlServer;
using BasicDotnetTemplate.MainProject.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace BasicDotnetTemplate.MainProject.Services; namespace BasicDotnetTemplate.MainProject.Services;
@@ -21,12 +22,15 @@ public interface IUserService
public class UserService : BaseService, IUserService public class UserService : BaseService, IUserService
{ {
private readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); private readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private readonly CryptUtils _cryptUtils;
public UserService( public UserService(
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
IConfiguration configuration, IConfiguration configuration,
SqlServerContext sqlServerContext SqlServerContext sqlServerContext
) : base(httpContextAccessor, configuration, sqlServerContext) ) : base(httpContextAccessor, configuration, sqlServerContext)
{ } {
this._cryptUtils = new(_appSettings);
}
private IQueryable<User> GetUsersQueryable() private IQueryable<User> GetUsersQueryable()
{ {
@@ -51,8 +55,9 @@ public class UserService : BaseService, IUserService
FirstName = data.FirstName, FirstName = data.FirstName,
LastName = data.LastName, LastName = data.LastName,
Email = data.Email, Email = data.Email,
PasswordSalt = "", PasswordSalt = _appSettings.EncryptionSettings?.Salt ?? String.Empty,
PasswordHash = "", PasswordPepper = CryptUtils.GeneratePepper(),
PasswordIterations = _appSettings.EncryptionSettings?.Iterations ?? 10,
Password = "", Password = "",
Role = role, Role = role,
IsTestUser = false IsTestUser = false
@@ -77,7 +82,9 @@ public class UserService : BaseService, IUserService
User? user = await this.GetUserByEmailQueryable(email).FirstOrDefaultAsync(); User? user = await this.GetUserByEmailQueryable(email).FirstOrDefaultAsync();
if (user != null) if (user != null)
{ {
var encryptedPassword = user.PasswordHash; var valid = CryptUtils.VerifyPassword(user.Password, password, user.PasswordSalt, user.PasswordIterations, user.PasswordPepper);
if (!valid)
user = null;
} }
return user; return user;

View File

@@ -4,24 +4,17 @@ using System.Text;
using BasicDotnetTemplate.MainProject.Models.Settings; using BasicDotnetTemplate.MainProject.Models.Settings;
namespace BasicDotnetTemplate.MainProject.Utils; namespace BasicDotnetTemplate.MainProject.Utils;
public class CryptUtils public class CryptUtils(AppSettings appSettings)
{ {
private readonly string _secretKey; private readonly string _secret = appSettings.EncryptionSettings?.Secret ?? String.Empty;
private readonly string _pepper;
private const int _M = 16; private const int _M = 16;
private const int _N = 32; private const int _N = 32;
public CryptUtils(AppSettings appSettings)
{
_secretKey = appSettings.EncryptionSettings?.Salt ?? String.Empty;
_pepper = appSettings.EncryptionSettings?.Pepper ?? String.Empty;
}
public string Decrypt(string encryptedData) public string Decrypt(string encryptedData)
{ {
var decrypted = String.Empty; var decrypted = String.Empty;
if (String.IsNullOrEmpty(this._secretKey) || this._secretKey.Length < _M) if (String.IsNullOrEmpty(this._secret) || this._secret.Length < _M)
{ {
throw new ArgumentException("Unable to proceed with decryption due to invalid settings"); throw new ArgumentException("Unable to proceed with decryption due to invalid settings");
} }
@@ -35,7 +28,7 @@ public class CryptUtils
using (var aes = Aes.Create()) using (var aes = Aes.Create())
{ {
aes.Key = Encoding.UTF8.GetBytes(this._secretKey); aes.Key = Encoding.UTF8.GetBytes(this._secret);
aes.IV = Encoding.UTF8.GetBytes(iv); aes.IV = Encoding.UTF8.GetBytes(iv);
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
@@ -57,21 +50,21 @@ public class CryptUtils
return decrypted; return decrypted;
} }
public static string GenerateSalt() public static string GeneratePepper()
{ {
using var rng = RandomNumberGenerator.Create(); using var rng = RandomNumberGenerator.Create();
var byteSalt = new byte[16]; var bytePepper = new byte[16];
rng.GetBytes(byteSalt); rng.GetBytes(bytePepper);
var salt = Convert.ToBase64String(byteSalt); var pepper = Convert.ToBase64String(bytePepper);
return salt; return pepper;
} }
public string GeneratePassword(string password, string salt, int iteration) public static string GeneratePassword(string password, string salt, int iterations, string? pepper = "")
{ {
string hashedPassword = password; string hashedPassword = password;
for(var i = 0; i <= iteration; i++) for (var i = 0; i <= iterations; i++)
{ {
var passwordSaltPepper = $"{hashedPassword}{salt}{this._pepper}"; var passwordSaltPepper = $"{hashedPassword}{salt}{pepper}";
var byteValue = Encoding.UTF8.GetBytes(passwordSaltPepper); var byteValue = Encoding.UTF8.GetBytes(passwordSaltPepper);
var byteHash = SHA256.HashData(byteValue); var byteHash = SHA256.HashData(byteValue);
hashedPassword = Convert.ToBase64String(byteHash); hashedPassword = Convert.ToBase64String(byteHash);
@@ -80,9 +73,9 @@ public class CryptUtils
return hashedPassword; return hashedPassword;
} }
public bool VerifyPassword(string password, string salt, int iteration, string userPassword) public static bool VerifyPassword(string userPassword, string password, string salt, int iterations, string? pepper = "")
{ {
string hashedPassword = this.GeneratePassword(password, salt, iteration); string hashedPassword = GeneratePassword(password, salt, iterations, pepper);
return hashedPassword.Equals(userPassword, StringComparison.OrdinalIgnoreCase); return hashedPassword.Equals(userPassword, StringComparison.OrdinalIgnoreCase);
} }

View File

@@ -35,8 +35,9 @@
"ExpiredAfterMinsOfInactivity": 15 "ExpiredAfterMinsOfInactivity": 15
}, },
"EncryptionSettings": { "EncryptionSettings": {
"Salt": "S7VIidfXQf1tOQYX", "Secret": "S7VIidfXQf1tOQYX",
"Pepper": "" "Salt": "",
"Iterations": 10
}, },
"PermissionsSettings": { "PermissionsSettings": {
"FilePath": "Config/permissions.json" "FilePath": "Config/permissions.json"