using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.Security; using Sarand.Net.Standard.Base.Utils; using Sarand.Net.Standard.Dbas.Data; namespace Sarand.Net.Standard.Dbas.Security { //======================================================================= internal partial class SaSecurityMgr_Dbas : SaSecurityMgr { //------------------------------------------------------------------- // Data members. //------------------------------------------------------------------- // Construction. internal SaSecurityMgr_Dbas(SaSecurityConsole_Dbas securityConsole) : base() { SecurityConsole = securityConsole; DbInfo = new DbDataItem.DatabaseInfo(securityConsole.Database); } //------------------------------------------------------------------- // Properties. internal SaSecurityConsole_Dbas SecurityConsole { get; } internal ISacsDatabase Database { get { return SecurityConsole.Database; } } private DbDataItem.DatabaseInfo DbInfo { get; } public String TableName_SecItem { get { return SaSecurityDbDfn.GetTableName(SaSecurityDbDfn.TableIdx.SaSecItem); } } public String TableName_SecLink { get { return SaSecurityDbDfn.GetTableName(SaSecurityDbDfn.TableIdx.SaSecLink); } } public override bool DatabaseIsValid { get { return (Database != null && Database.GetTableNames().Contains(TableName_SecItem) && Database.GetTableNames().Contains(TableName_SecLink)); } } public override bool CanUpdateDatabase { get { return DatabaseIsValid; } } // Database utilities. public override bool ColumnExists(SaSecurityDbDfn.ColumnIdx columnIdx) { String tableName = SaSecurityDbDfn.GetTableName(SaSecurityDbDfn.GetTableForColumn(columnIdx), false); String columnName = SaSecurityDbDfn.GetColumnName(columnIdx, false); return Database.ColumnExists(tableName, columnName); } //------------------------------------------------------------------- // Password utilities. /// Store the password against against the user. public override bool SetPassword(SaSecurityItem securityItemUser, String password) { SaSecurity.PasswordStorageMethod method = SaSecurity.PasswordStorageMethod.Legacy; // If the Password Salt/Hash columns exist, attempt to encode password using non-legacy method. if (ColumnExists(SaSecurityDbDfn.ColumnIdx.SaSecItem_PwordSalt) && ColumnExists(SaSecurityDbDfn.ColumnIdx.SaSecItem_PwordHash)) { // NB Do not use Microsoft.AspNetCore.Identity.PasswordHasher.HashPassword() while legacy (C++) desktop application still in use. // Encode password using MS SQL utilities. String passwordHashHex_MsSql = EncodePassword_Hex(password, SaSecurity.PasswordHashMethod.MsSql); if (!StringUtil.IsBlank(passwordHashHex_MsSql) && ComparePassword_Hex(password, passwordHashHex_MsSql, SaSecurity.PasswordHashMethod.MsSql)) { // MS SQL encoded password is valid. securityItemUser.SetEncodedPassword(SaSecurity.PasswordStorageMethod.MsSql, passwordHashHex_MsSql); method = SaSecurity.PasswordStorageMethod.MsSql; } } if (method == SaSecurity.PasswordStorageMethod.Legacy) { // Default to legacy method. securityItemUser.SetEncodedPassword(SaSecurity.PasswordStorageMethod.Legacy, EncodePassword_Legacy(password)); } return false; } /// Is the password currently defined against the user blank. public override bool PasswordIsBlank(SaSecurityItem securityItemUser) { return PasswordMatches(securityItemUser, "", null); } /// Does the password match that currently defined against the user. public override bool PasswordMatches(SaSecurityItem securityItemUser, String password, DbUpdateInfo dbUpdateInfo) { if (securityItemUser == null) return false; SaSecurity.PasswordStorageMethod method = securityItemUser.GetPasswordMethod(); if (method == SaSecurity.PasswordStorageMethod.MsSql) { // MsSql method. return ComparePassword_Hex(password, securityItemUser.PasswordHash, SaSecurity.PasswordHashMethod.MsSql); } // Legacy method. #pragma warning disable CS0618 // Deprecated property bool matches = (EncodePassword_Legacy(password).Equals(securityItemUser.PasswordEncrypted)); #pragma warning restore CS0618 // Deprecated property if (matches && !StringUtil.IsBlank(password)) { SetPassword(securityItemUser, password); if (securityItemUser.GetPasswordMethod() != SaSecurity.PasswordStorageMethod.Legacy) UpdateSecurityItem(securityItemUser, dbUpdateInfo); } return matches; } internal static String EncodePassword_Legacy(String password) { // We know this is not very water-tight, but it at least // hides the contents of the password from prying eyes. String toBeEncrypted = password; // Password length goes in as first character: toBeEncrypted = ByteToString((byte)(128 - password.Length)) + toBeEncrypted; toBeEncrypted = string.Concat(toBeEncrypted.Reverse()); // Pad out to fixed length (minimum 16, multiples of 8): int fixedLength = 16; if (password.Length / 8 > 2) fixedLength = 8 * (int)(password.Length / 8); while (toBeEncrypted.Length < fixedLength) { char charToAdd = (char)(password.Length > 0 ? password[(toBeEncrypted.Length % password.Length)] : 'A' + (toBeEncrypted.Length * 13) % 23); if (toBeEncrypted.Length % 2 != 0) toBeEncrypted += charToAdd; else toBeEncrypted = charToAdd + toBeEncrypted; } // Now perform a simple algorythm on each character to hide its true value: byte[] encryptedChars = new byte[toBeEncrypted.Length]; for (int i = 0; i < toBeEncrypted.Length; i++) { int iData = toBeEncrypted[i]; iData += 17 + i; if (iData > 255) iData -= (255 + 32); encryptedChars[i] = (byte)iData; } String result = Encoding_Ansi1252.GetString(encryptedChars); #if DEBUG // Password: mgbgt // To encrypt: tbmggtgbgm{btggm // Encrypted: 133, 116, 128, 123, 124, 138, 126, 122, 128, 135, 150, 126, 145, 133, 134, 141 //Logger.Info(fn + "\n\tPassword: " + password + "\n\tTo encrypt: " + toBeEncrypted + "\n\tEncrypted: " + encryptedChars.Select(i => i.ToString()).ToList().Concat(", ")); //Logger.Debug(password + ": " + result); #endif return result; } private static byte[] PasswordStringToBytes(String value) { byte[] values = new byte[value.Length]; for (int idx = 0; idx < value.Length; idx++) { char c = value[idx]; UInt32 ui = Convert.ToUInt32(c); SaLogger.Info(idx + ". " + ui.ToString("x8")); values[idx] = (byte)(c); } return values; } private static readonly Encoding Encoding_Ansi1252 = Encoding.GetEncoding(1252); private static String ByteToString(byte value) { return Encoding_Ansi1252.GetString(new[] { value }); } //------------------------------------------------------------------- // Password Database utilities. //------------------------------------------------------------------- /// /// Generate password hash using the specified method. /// - Returns hex notation as a string. /// /// /// String of hex digits corresponding to encrypted password. public override String EncodePassword_Hex(String password, SaSecurity.PasswordHashMethod method = SaSecurity.PasswordHashMethod.MsSql) { ClearErrorState(); if (method == SaSecurity.PasswordHashMethod.MsSql) { if (Database.DbmsType == DbmsType.MsSql) { try { String colAlias = "DataValue"; String query = $"Select {colAlias}=Convert(VarChar(2000),PwdEncrypt('{DbQuery.FormatForCondition(password)}'),2);"; using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(Database, query)) { if (dbResultSet.NextRecord()) return dbResultSet.GetRecordValues().GetValue(colAlias).ToString(); else if (dbResultSet.HasErrorState()) SetErrorState(dbResultSet.ErrorStateMessage); } } catch (Exception exc) { SaLogger.Error($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc, "hashing password")}", true); SetErrorState(exc.Message); } } else SaLogger.Warn($"{SysUtil.Fn} Unable to encode password by method 'SQL'. Database is '{Database.DbmsType}'."); } else SaLogger.Warn($"{SysUtil.Fn} Unable to encode password. Unrecognised method: '{method}'."); return ""; } /// /// Compare the password to the encrypted hash using the specified method. /// /// /// /// True if the password matches the encrypted hash value. public override bool ComparePassword_Hex(String password, String passwordHashHex, SaSecurity.PasswordHashMethod method = SaSecurity.PasswordHashMethod.MsSql) { ClearErrorState(); if (method == SaSecurity.PasswordHashMethod.MsSql) { if (Database.DbmsType == DbmsType.MsSql) { try { String colAlias = "PwdCompare"; String query = $"Select {colAlias}=PwdCompare('{DbQuery.FormatForCondition(password)}', Convert(VarBinary(1000),'{DbQuery.FormatForCondition(passwordHashHex)}',2));"; using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(Database, query)) { if (dbResultSet.NextRecord()) return StringUtil.ToBool(dbResultSet.GetRecordValues().GetValue(colAlias).ToString()); else if (dbResultSet.HasErrorState()) SetErrorState(dbResultSet.ErrorStateMessage); } } catch (Exception exc) { SaLogger.Error($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc, "comparing password")}", true); SetErrorState(exc.Message); } } else SaLogger.Warn($"{SysUtil.Fn} Unable to compare password by method 'SQL'. Database is '{Database.DbmsType}'."); } else SaLogger.Warn($"{SysUtil.Fn} Unable to compare password. Unrecognised method: '{method}'."); return false; } //------------------------------------------------------------------- } //======================================================================= }