mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-09-20 14:32:05 +00:00
Implement 2 additional crypto methods for Steam password
Inspiration by @legendofmiracles
This commit is contained in:
parent
b030755eb6
commit
e68210cf2e
4 changed files with 97 additions and 50 deletions
|
@ -24,9 +24,11 @@ using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.Localization;
|
using ArchiSteamFarm.Localization;
|
||||||
using CryptSharp.Utility;
|
using CryptSharp.Utility;
|
||||||
|
@ -57,7 +59,7 @@ public static class ArchiCryptoHelper {
|
||||||
|
|
||||||
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm));
|
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm));
|
||||||
|
|
||||||
internal static string? Decrypt(ECryptoMethod cryptoMethod, string encryptedString) {
|
internal static async Task<string?> Decrypt(ECryptoMethod cryptoMethod, string encryptedString) {
|
||||||
if (!Enum.IsDefined(typeof(ECryptoMethod), cryptoMethod)) {
|
if (!Enum.IsDefined(typeof(ECryptoMethod), cryptoMethod)) {
|
||||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||||
}
|
}
|
||||||
|
@ -67,8 +69,10 @@ public static class ArchiCryptoHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
return cryptoMethod switch {
|
return cryptoMethod switch {
|
||||||
ECryptoMethod.PlainText => encryptedString,
|
|
||||||
ECryptoMethod.AES => DecryptAES(encryptedString),
|
ECryptoMethod.AES => DecryptAES(encryptedString),
|
||||||
|
ECryptoMethod.EnvironmentVariable => Environment.GetEnvironmentVariable(encryptedString)?.Trim(),
|
||||||
|
ECryptoMethod.File => await ReadFromFile(encryptedString).ConfigureAwait(false),
|
||||||
|
ECryptoMethod.PlainText => encryptedString,
|
||||||
ECryptoMethod.ProtectedDataForCurrentUser => DecryptProtectedDataForCurrentUser(encryptedString),
|
ECryptoMethod.ProtectedDataForCurrentUser => DecryptProtectedDataForCurrentUser(encryptedString),
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(cryptoMethod))
|
_ => throw new ArgumentOutOfRangeException(nameof(cryptoMethod))
|
||||||
};
|
};
|
||||||
|
@ -84,8 +88,10 @@ public static class ArchiCryptoHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
return cryptoMethod switch {
|
return cryptoMethod switch {
|
||||||
ECryptoMethod.PlainText => decryptedString,
|
|
||||||
ECryptoMethod.AES => EncryptAES(decryptedString),
|
ECryptoMethod.AES => EncryptAES(decryptedString),
|
||||||
|
ECryptoMethod.EnvironmentVariable => decryptedString,
|
||||||
|
ECryptoMethod.File => decryptedString,
|
||||||
|
ECryptoMethod.PlainText => decryptedString,
|
||||||
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(decryptedString),
|
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(decryptedString),
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(cryptoMethod))
|
_ => throw new ArgumentOutOfRangeException(nameof(cryptoMethod))
|
||||||
};
|
};
|
||||||
|
@ -141,6 +147,13 @@ public static class ArchiCryptoHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool HasTransformation(this ECryptoMethod cryptoMethod) =>
|
||||||
|
cryptoMethod switch {
|
||||||
|
ECryptoMethod.AES => true,
|
||||||
|
ECryptoMethod.ProtectedDataForCurrentUser => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
|
||||||
internal static string? RecoverSteamParentalCode(byte[] passwordHash, byte[] salt, EHashingMethod hashingMethod) {
|
internal static string? RecoverSteamParentalCode(byte[] passwordHash, byte[] salt, EHashingMethod hashingMethod) {
|
||||||
if ((passwordHash == null) || (passwordHash.Length == 0)) {
|
if ((passwordHash == null) || (passwordHash.Length == 0)) {
|
||||||
throw new ArgumentNullException(nameof(passwordHash));
|
throw new ArgumentNullException(nameof(passwordHash));
|
||||||
|
@ -276,10 +289,34 @@ public static class ArchiCryptoHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<string?> ReadFromFile(string filePath) {
|
||||||
|
if (string.IsNullOrEmpty(filePath)) {
|
||||||
|
throw new ArgumentNullException(nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(filePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string text;
|
||||||
|
|
||||||
|
try {
|
||||||
|
text = await File.ReadAllTextAsync(filePath).ConfigureAwait(false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ASF.ArchiLogger.LogGenericException(e);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
public enum ECryptoMethod : byte {
|
public enum ECryptoMethod : byte {
|
||||||
PlainText,
|
PlainText,
|
||||||
AES,
|
AES,
|
||||||
ProtectedDataForCurrentUser
|
ProtectedDataForCurrentUser,
|
||||||
|
EnvironmentVariable,
|
||||||
|
File
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EHashingMethod : byte {
|
public enum EHashingMethod : byte {
|
||||||
|
|
|
@ -125,7 +125,7 @@ public sealed class BotController : ArchiController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.BotConfig.IsSteamPasswordSet && bot.BotConfig.IsSteamPasswordSet) {
|
if (!request.BotConfig.IsSteamPasswordSet && bot.BotConfig.IsSteamPasswordSet) {
|
||||||
request.BotConfig.DecryptedSteamPassword = bot.BotConfig.DecryptedSteamPassword;
|
request.BotConfig.SetDecryptedSteamPassword(await bot.BotConfig.GetDecryptedSteamPassword().ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.BotConfig.IsSteamParentalCodeSet && bot.BotConfig.IsSteamParentalCodeSet) {
|
if (!request.BotConfig.IsSteamParentalCodeSet && bot.BotConfig.IsSteamParentalCodeSet) {
|
||||||
|
|
|
@ -180,7 +180,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Login keys are not guaranteed to be valid, we should use them only if we don't have full details available from the user
|
/// Login keys are not guaranteed to be valid, we should use them only if we don't have full details available from the user
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private bool ShouldUseLoginKeys => BotConfig.UseLoginKeys && (!BotConfig.IsSteamPasswordSet || string.IsNullOrEmpty(BotConfig.DecryptedSteamPassword) || !HasMobileAuthenticator);
|
private bool ShouldUseLoginKeys => BotConfig.UseLoginKeys && (!BotConfig.IsSteamPasswordSet || !HasMobileAuthenticator);
|
||||||
|
|
||||||
[JsonProperty(PropertyName = $"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(SteamID)}")]
|
[JsonProperty(PropertyName = $"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(SteamID)}")]
|
||||||
private string SSteamID => SteamID.ToString(CultureInfo.InvariantCulture);
|
private string SSteamID => SteamID.ToString(CultureInfo.InvariantCulture);
|
||||||
|
@ -863,7 +863,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ASF.EUserInputType.Password:
|
case ASF.EUserInputType.Password:
|
||||||
BotConfig.DecryptedSteamPassword = inputValue;
|
BotConfig.SetDecryptedSteamPassword(inputValue, true);
|
||||||
BotConfig.IsSteamPasswordSet = false;
|
BotConfig.IsSteamPasswordSet = false;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -2033,16 +2033,20 @@ public sealed class Bot : IAsyncDisposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiresPassword && string.IsNullOrEmpty(BotConfig.DecryptedSteamPassword)) {
|
if (requiresPassword) {
|
||||||
RequiredInput = ASF.EUserInputType.Password;
|
string? decryptedSteamPassword = await BotConfig.GetDecryptedSteamPassword().ConfigureAwait(false);
|
||||||
|
|
||||||
string? steamPassword = await Logging.GetUserInput(ASF.EUserInputType.Password, BotName).ConfigureAwait(false);
|
if (string.IsNullOrEmpty(decryptedSteamPassword)) {
|
||||||
|
RequiredInput = ASF.EUserInputType.Password;
|
||||||
|
|
||||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
string? steamPassword = await Logging.GetUserInput(ASF.EUserInputType.Password, BotName).ConfigureAwait(false);
|
||||||
if (string.IsNullOrEmpty(steamPassword) || !SetUserInput(ASF.EUserInputType.Password, steamPassword!)) {
|
|
||||||
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(steamPassword)));
|
|
||||||
|
|
||||||
return false;
|
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||||
|
if (string.IsNullOrEmpty(steamPassword) || !SetUserInput(ASF.EUserInputType.Password, steamPassword!)) {
|
||||||
|
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(steamPassword)));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2256,8 +2260,8 @@ public sealed class Bot : IAsyncDisposable {
|
||||||
|
|
||||||
// Decrypt login key if needed
|
// Decrypt login key if needed
|
||||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||||
if (!string.IsNullOrEmpty(loginKey) && (loginKey!.Length > 19) && (BotConfig.PasswordFormat != ArchiCryptoHelper.ECryptoMethod.PlainText)) {
|
if (!string.IsNullOrEmpty(loginKey) && (loginKey!.Length > 19) && BotConfig.PasswordFormat.HasTransformation()) {
|
||||||
loginKey = ArchiCryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey);
|
loginKey = await ArchiCryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If we're not using login keys, ensure we don't have any saved
|
// If we're not using login keys, ensure we don't have any saved
|
||||||
|
@ -2287,7 +2291,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? password = BotConfig.DecryptedSteamPassword;
|
string? password = await BotConfig.GetDecryptedSteamPassword().ConfigureAwait(false);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(password)) {
|
if (!string.IsNullOrEmpty(password)) {
|
||||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||||
|
|
|
@ -273,37 +273,6 @@ public sealed class BotConfig {
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string? DecryptedSteamPassword {
|
|
||||||
get {
|
|
||||||
if (string.IsNullOrEmpty(SteamPassword)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PasswordFormat == ArchiCryptoHelper.ECryptoMethod.PlainText) {
|
|
||||||
return SteamPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
string? result = ArchiCryptoHelper.Decrypt(PasswordFormat, SteamPassword!);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(result)) {
|
|
||||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(SteamPassword)));
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
if (!string.IsNullOrEmpty(value) && (PasswordFormat != ArchiCryptoHelper.ECryptoMethod.PlainText)) {
|
|
||||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
|
||||||
value = ArchiCryptoHelper.Encrypt(PasswordFormat, value!);
|
|
||||||
}
|
|
||||||
|
|
||||||
SteamPassword = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsSteamLoginSet { get; set; }
|
internal bool IsSteamLoginSet { get; set; }
|
||||||
internal bool IsSteamParentalCodeSet { get; set; }
|
internal bool IsSteamParentalCodeSet { get; set; }
|
||||||
internal bool IsSteamPasswordSet { get; set; }
|
internal bool IsSteamPasswordSet { get; set; }
|
||||||
|
@ -530,6 +499,26 @@ public sealed class BotConfig {
|
||||||
return !Enum.IsDefined(typeof(ArchiHandler.EUserInterfaceMode), UserInterfaceMode) ? (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(UserInterfaceMode), UserInterfaceMode)) : (true, null);
|
return !Enum.IsDefined(typeof(ArchiHandler.EUserInterfaceMode), UserInterfaceMode) ? (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(UserInterfaceMode), UserInterfaceMode)) : (true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task<string?> GetDecryptedSteamPassword() {
|
||||||
|
if (string.IsNullOrEmpty(SteamPassword)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PasswordFormat == ArchiCryptoHelper.ECryptoMethod.PlainText) {
|
||||||
|
return SteamPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? result = await ArchiCryptoHelper.Decrypt(PasswordFormat, SteamPassword!).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(result)) {
|
||||||
|
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(SteamPassword)));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
internal static async Task<(BotConfig? BotConfig, string? LatestJson)> Load(string filePath) {
|
internal static async Task<(BotConfig? BotConfig, string? LatestJson)> Load(string filePath) {
|
||||||
if (string.IsNullOrEmpty(filePath)) {
|
if (string.IsNullOrEmpty(filePath)) {
|
||||||
throw new ArgumentNullException(nameof(filePath));
|
throw new ArgumentNullException(nameof(filePath));
|
||||||
|
@ -575,7 +564,9 @@ public sealed class BotConfig {
|
||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(botConfig.DecryptedSteamPassword)) {
|
string? decryptedSteamPassword = await botConfig.GetDecryptedSteamPassword().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(decryptedSteamPassword)) {
|
||||||
HashSet<string> disallowedValues = new(StringComparer.InvariantCultureIgnoreCase) { "account" };
|
HashSet<string> disallowedValues = new(StringComparer.InvariantCultureIgnoreCase) { "account" };
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(botConfig.SteamLogin)) {
|
if (!string.IsNullOrEmpty(botConfig.SteamLogin)) {
|
||||||
|
@ -584,7 +575,8 @@ public sealed class BotConfig {
|
||||||
|
|
||||||
Utilities.InBackground(
|
Utilities.InBackground(
|
||||||
() => {
|
() => {
|
||||||
(bool isWeak, string? reason) = Utilities.TestPasswordStrength(botConfig.DecryptedSteamPassword!, disallowedValues);
|
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||||
|
(bool isWeak, string? reason) = Utilities.TestPasswordStrength(decryptedSteamPassword!, disallowedValues);
|
||||||
|
|
||||||
if (isWeak) {
|
if (isWeak) {
|
||||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningWeakSteamPassword, !string.IsNullOrEmpty(botConfig.SteamLogin) ? botConfig.SteamLogin! : filePath, reason));
|
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningWeakSteamPassword, !string.IsNullOrEmpty(botConfig.SteamLogin) ? botConfig.SteamLogin! : filePath, reason));
|
||||||
|
@ -615,6 +607,20 @@ public sealed class BotConfig {
|
||||||
return (botConfig, json != latestJson ? latestJson : null);
|
return (botConfig, json != latestJson ? latestJson : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void SetDecryptedSteamPassword(string? decryptedSteamPassword, bool fromUser = false) {
|
||||||
|
if (!string.IsNullOrEmpty(decryptedSteamPassword) && PasswordFormat.HasTransformation()) {
|
||||||
|
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||||
|
decryptedSteamPassword = ArchiCryptoHelper.Encrypt(PasswordFormat, decryptedSteamPassword!);
|
||||||
|
}
|
||||||
|
|
||||||
|
SteamPassword = decryptedSteamPassword;
|
||||||
|
|
||||||
|
if (fromUser) {
|
||||||
|
// Reset steam password set flag, it actually isn't set in the config
|
||||||
|
IsSteamPasswordSet = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum EAccess : byte {
|
public enum EAccess : byte {
|
||||||
None,
|
None,
|
||||||
FamilySharing,
|
FamilySharing,
|
||||||
|
|
Loading…
Reference in a new issue