diff --git a/.vs/SecureCore/v16/.suo b/.vs/SecureCore/v16/.suo index d6d28df..4fe6fcb 100644 Binary files a/.vs/SecureCore/v16/.suo and b/.vs/SecureCore/v16/.suo differ diff --git a/SecureCore/Authentication.cs b/SecureCore/Authentication.cs deleted file mode 100644 index 19b29fb..0000000 --- a/SecureCore/Authentication.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using System.Security.Cryptography; -using Microsoft.AspNetCore.Cryptography.KeyDerivation; -using SecureCore.Services; - -namespace SecureCore -{ - public class Authentication - { - private static int Iterations { get; set; } = 100000; - private static KeyDerivationPrf KeyType { get; } = KeyDerivationPrf.HMACSHA512; - private static int KeySize { get; } = 512 / 8; - private static int SaltSize { get; } = 128 / 8; //128 bit salt - private static int SessionKeySize { get; } = 32; //32 bytes - - public static string CreateSessionToken() - { - var token = new byte[SessionKeySize]; - - GetRandomBytes(ref token); - - return Convert.ToBase64String(token); - } - - public static (string Hash, string Salt) HashPassword(string password) - { - var salt = new byte[SaltSize]; - - GetRandomBytes(ref salt); - - return (GetPasswordHash(password, salt), Convert.ToBase64String(salt)); - } - - public static bool PasswordIsValid(string password, string salt, string passwordHash) - { - var saltBytes = Convert.FromBase64String(salt); - - return passwordHash == GetPasswordHash(password, saltBytes); - } - - public static bool IsAllowed(HttpContext context) - { - if (!context.Request.Cookies.ContainsKey("Session")) return false; - - return UserDataService.IsSessionTokenValid(context.Request.Cookies["Session"]); - } - - private static string GetPasswordHash(string password, byte[] salt) - { - return Convert.ToBase64String(KeyDerivation.Pbkdf2(password, salt, KeyType, Iterations, KeySize)); - } - - private static void GetRandomBytes(ref byte[] bytes) - { - using var rng = RandomNumberGenerator.Create(); - rng.GetBytes(bytes); - } - } - - public class LoginInfo - { - public string UserName { get; set; } - public string Password { get; set; } - public string Email { get; set; } - } -} diff --git a/SecureCore/Authentication/ByteGenerator.cs b/SecureCore/Authentication/ByteGenerator.cs new file mode 100644 index 0000000..56044ea --- /dev/null +++ b/SecureCore/Authentication/ByteGenerator.cs @@ -0,0 +1,13 @@ +using System.Security.Cryptography; + +namespace SecureCore.Authentication +{ + public static class ByteGenerator + { + public static void GetRandomBytes(ref byte[] bytes) + { + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(bytes); + } + } +} diff --git a/SecureCore/Authentication/PasswordManager.cs b/SecureCore/Authentication/PasswordManager.cs new file mode 100644 index 0000000..9200720 --- /dev/null +++ b/SecureCore/Authentication/PasswordManager.cs @@ -0,0 +1,57 @@ +using System; +using System.Data.SqlClient; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace SecureCore.Authentication +{ + public class PasswordManager + { + private static string ConnectionString = @"Server=DESKTOP-OEDDVKC\SQLEXPRESS;Database=main;Integrated Security=true;"; + + public PasswordManager() + { + } + + public static (string Hash, string Salt) HashPassword(string password) + { + var salt = new byte[Settings.SaltSize]; + + ByteGenerator.GetRandomBytes(ref salt); + + return (GetPasswordHash(password, salt), Convert.ToBase64String(salt)); + } + + public static bool PasswordIsValid(string password, string salt, string passwordHash) + { + var saltBytes = Convert.FromBase64String(salt); + + return passwordHash == GetPasswordHash(password, saltBytes); + } + + public static (string PasswordHash, string SaltHash) GetUserPasswordHash(int userId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + using (var command = new SqlCommand("SELECT [Password Hash], [Salt Hash] FROM Login WHERE [User Key] = @UserId", connection)) + { + command.Parameters.AddWithValue("UserId", userId); + + connection.Open(); + + var reader = command.ExecuteReader(); + + if (!reader.HasRows) throw new MissingFieldException("No login records exist for this user."); + + reader.Read(); + + return (reader["Password Hash"].ToString(), reader["Salt Hash"].ToString()); + } + } + } + + private static string GetPasswordHash(string password, byte[] salt) + { + return Convert.ToBase64String(KeyDerivation.Pbkdf2(password, salt, Settings.KeyType, Settings.Iterations, Settings.KeySize)); + } + } +} diff --git a/SecureCore/Authentication/SessionManager.cs b/SecureCore/Authentication/SessionManager.cs new file mode 100644 index 0000000..5009182 --- /dev/null +++ b/SecureCore/Authentication/SessionManager.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SecureCore.Authentication +{ + public class SessionManager + { + public static string CreateSessionToken() + { + var token = new byte[Settings.SessionKeySize]; + + ByteGenerator.GetRandomBytes(ref token); + + return Convert.ToBase64String(token); + } + } +} diff --git a/SecureCore/Authentication/Settings.cs b/SecureCore/Authentication/Settings.cs new file mode 100644 index 0000000..bb3ce7b --- /dev/null +++ b/SecureCore/Authentication/Settings.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace SecureCore.Authentication +{ + public static class Settings + { + public static int Iterations { get; } = 100000; + public static KeyDerivationPrf KeyType { get; } = KeyDerivationPrf.HMACSHA512; + public static int KeySize { get; } = 512 / 8; + public static int SaltSize { get; } = 128 / 8; //128 bit salt + public static int SessionKeySize { get; } = 32; //32 bytes + } +} diff --git a/SecureCore/Controllers/AuthController.cs b/SecureCore/Controllers/AuthController.cs index 7f6ea92..b1b4bda 100644 --- a/SecureCore/Controllers/AuthController.cs +++ b/SecureCore/Controllers/AuthController.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using SecureCore.Services; +using SecureCore.Authentication; namespace SecureCore.Controllers { @@ -17,9 +18,12 @@ namespace SecureCore.Controllers [AcceptVerbs("POST")] public IActionResult Login(LoginInfo info) { + //Very the user has login data. + if (!UserDataService.UserHasLoginData(info.UserName)) return Unauthorized("User doesn't have login creds"); + var (password, saltHash) = UserDataService.GetUserPasswordHash(info.UserName); - if (Authentication.PasswordIsValid(info.Password, saltHash, password)) + if (PasswordManager.PasswordIsValid(info.Password, saltHash, password)) { if (HttpContext.Request.Cookies.ContainsKey("Session")) { @@ -27,7 +31,7 @@ namespace SecureCore.Controllers return Ok($"Session is live{Environment.NewLine}"); } - var sessionToken = Authentication.CreateSessionToken(); + var sessionToken = SessionManager.CreateSessionToken(); UserDataService.SetUserSessionToken(UserDataService.GetUserId(info.UserName), sessionToken, DateTime.Now.AddDays(7)); @@ -45,8 +49,8 @@ namespace SecureCore.Controllers [AcceptVerbs("POST")] public IActionResult Register(LoginInfo info) { - var (hash, salt) = Authentication.HashPassword(info.Password); - var sessionToken = Authentication.CreateSessionToken(); + var (hash, salt) = PasswordManager.HashPassword(info.Password); + var sessionToken = SessionManager.CreateSessionToken(); try { @@ -62,6 +66,17 @@ namespace SecureCore.Controllers } } + [HttpPost("Logout")] + [AcceptVerbs("POST")] + public IActionResult Logout(LoginInfo info) + { + UserDataService.DestorySession(HttpContext.Request.Cookies["Session"]); + + HttpContext.Response.Cookies.Delete("Session"); + + return Ok(); + } + private CookieOptions GetCookieOptions() { return new CookieOptions @@ -76,6 +91,13 @@ namespace SecureCore.Controllers } } + public class LoginInfo + { + public string UserName { get; set; } + public string Password { get; set; } + public string Email { get; set; } + } + //TODO: Read more https://www.valentinog.com/blog/cookies/ // And this https://blog.webf.zone/ultimate-guide-to-http-cookies-2aa3e083dbae } diff --git a/SecureCore/Controllers/EmployeeController.cs b/SecureCore/Controllers/EmployeeController.cs index 3189e55..8759034 100644 --- a/SecureCore/Controllers/EmployeeController.cs +++ b/SecureCore/Controllers/EmployeeController.cs @@ -5,8 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using SecureCore.Services; -using System.Security.Cryptography; -using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using SecureCore.Authentication; namespace SecureCore.Controllers { @@ -25,19 +24,19 @@ namespace SecureCore.Controllers [HttpGet] public IActionResult Get() { - if (Authentication.IsAllowed(HttpContext)) - return Ok(DataService.Get()); - else - return Unauthorized(); + //if (Authentication.IsAllowed(HttpContext)) + return Ok(DataService.Get()); + //else + // return Unauthorized(); } [HttpGet("{id}", Name = "Get")] public IActionResult Get(int id) { - if (Authentication.IsAllowed(HttpContext)) - return Ok(DataService.GetById(id)); - else - return Unauthorized(); + //if (Authentication.IsAllowed(HttpContext)) + return Ok(DataService.GetById(id)); + //else + // return Unauthorized(); } } diff --git a/SecureCore/Program.cs b/SecureCore/Program.cs index 8739c1c..39079a5 100644 --- a/SecureCore/Program.cs +++ b/SecureCore/Program.cs @@ -13,16 +13,9 @@ namespace SecureCore { public static void Main(string[] args) { - //CreateHostBuilder(args).Build().Run(); CreateWebHostBuilder(args).Run(); } - //public static IHostBuilder CreateHostBuilder(string[] args) => - // Host.CreateDefaultBuilder(args) - // .ConfigureWebHostDefaults(webBuilder => - // { - // webBuilder.UseStartup(); - // }); public static IWebHost CreateWebHostBuilder(string[] args) { return new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).UseUrls("http://*:5000").UseIISIntegration().UseStartup().Build(); diff --git a/SecureCore/Services/UserDataService.cs b/SecureCore/Services/UserDataService.cs index b35b4a2..0db4dcf 100644 --- a/SecureCore/Services/UserDataService.cs +++ b/SecureCore/Services/UserDataService.cs @@ -33,6 +33,25 @@ namespace SecureCore.Services } } + public static bool UserHasLoginData(string userName) + { + var userId = GetUserId(userName); + + using (var connection = new SqlConnection(ConnectionString)) + { + using (var command = new SqlCommand("SELECT [Password Hash], [Salt Hash] FROM Login WHERE [User Key] = @UserId", connection)) + { + command.Parameters.AddWithValue("UserId", userId); + + connection.Open(); + + var reader = command.ExecuteReader(); + + return reader.HasRows; + } + } + } + public static int GetUserId(string userName) { using (var connection = new SqlConnection(ConnectionString)) @@ -121,5 +140,20 @@ namespace SecureCore.Services } } } + + public static void DestorySession(string sessionToken) + { + using (var connection = new SqlConnection(ConnectionString)) + { + using (var command = new SqlCommand("DELETE FROM [Session] WHERE [Session Token] = @SessionToken", connection)) + { + command.Parameters.AddWithValue("SessionToken", sessionToken); + + connection.Open(); + + command.ExecuteNonQuery(); + } + } + } } } diff --git a/SecureCore/Startup.cs b/SecureCore/Startup.cs index 6144ea4..b0fbc97 100644 --- a/SecureCore/Startup.cs +++ b/SecureCore/Startup.cs @@ -28,7 +28,6 @@ namespace SecureCore { services.AddTransient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest); - //services.AddControllers(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/SecureCore/appsettings.json b/SecureCore/appsettings.json index d9d9a9b..e77600c 100644 --- a/SecureCore/appsettings.json +++ b/SecureCore/appsettings.json @@ -6,5 +6,6 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionString": "Server=DESKTOP-OEDDVKC\\SQLEXPRESS;Database=main;Integrated Security=true;" } diff --git a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csproj.CoreCompileInputs.cache b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csproj.CoreCompileInputs.cache index 0771db9..20b0a63 100644 --- a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csproj.CoreCompileInputs.cache +++ b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -4d9579093cc42096d4f65291f56f6cb3edc17317 +390c9f5585bc7dcf8f0c5f65f443d23f17063571 diff --git a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csprojAssemblyReference.cache b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csprojAssemblyReference.cache index bd1b30c..c26662d 100644 Binary files a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csprojAssemblyReference.cache and b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.csprojAssemblyReference.cache differ diff --git a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.dll b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.dll index a279d83..97e1b27 100644 Binary files a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.dll and b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.dll differ diff --git a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.pdb b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.pdb index 31edcdb..39177d0 100644 Binary files a/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.pdb and b/SecureCore/obj/Debug/netcoreapp3.1/SecureCore.pdb differ