242 lines
10 KiB
C#
242 lines
10 KiB
C#
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.
|
|
|
|
/// <summary>Store the password against against the user.</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Is the password currently defined against the user blank.</summary>
|
|
public override bool PasswordIsBlank(SaSecurityItem securityItemUser)
|
|
{
|
|
return PasswordMatches(securityItemUser, "", null);
|
|
}
|
|
|
|
/// <summary>Does the password match that currently defined against the user.</summary>
|
|
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.
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Generate password hash using the specified method.
|
|
/// <para>- Returns hex notation as a string.</para>
|
|
/// </summary>
|
|
/// <param name="password"></param>
|
|
/// <returns>String of hex digits corresponding to encrypted password.</returns>
|
|
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 "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compare the password to the encrypted hash using the specified method.
|
|
/// </summary>
|
|
/// <param name="password"></param>
|
|
/// <param name="passwordHashHex"></param>
|
|
/// <returns>True if the password matches the encrypted hash value.</returns>
|
|
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;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|