mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-11-10 15:14:41 +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.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using CryptSharp.Utility;
|
||||
|
@ -57,7 +59,7 @@ public static class ArchiCryptoHelper {
|
|||
|
||||
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)) {
|
||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||
}
|
||||
|
@ -67,8 +69,10 @@ public static class ArchiCryptoHelper {
|
|||
}
|
||||
|
||||
return cryptoMethod switch {
|
||||
ECryptoMethod.PlainText => 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),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(cryptoMethod))
|
||||
};
|
||||
|
@ -84,8 +88,10 @@ public static class ArchiCryptoHelper {
|
|||
}
|
||||
|
||||
return cryptoMethod switch {
|
||||
ECryptoMethod.PlainText => decryptedString,
|
||||
ECryptoMethod.AES => EncryptAES(decryptedString),
|
||||
ECryptoMethod.EnvironmentVariable => decryptedString,
|
||||
ECryptoMethod.File => decryptedString,
|
||||
ECryptoMethod.PlainText => decryptedString,
|
||||
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(decryptedString),
|
||||
_ => 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) {
|
||||
if ((passwordHash == null) || (passwordHash.Length == 0)) {
|
||||
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 {
|
||||
PlainText,
|
||||
AES,
|
||||
ProtectedDataForCurrentUser
|
||||
ProtectedDataForCurrentUser,
|
||||
EnvironmentVariable,
|
||||
File
|
||||
}
|
||||
|
||||
public enum EHashingMethod : byte {
|
||||
|
|
|
@ -125,7 +125,7 @@ public sealed class BotController : ArchiController {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -180,7 +180,7 @@ public sealed class Bot : IAsyncDisposable {
|
|||
/// <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
|
||||
/// </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)}")]
|
||||
private string SSteamID => SteamID.ToString(CultureInfo.InvariantCulture);
|
||||
|
@ -863,7 +863,7 @@ public sealed class Bot : IAsyncDisposable {
|
|||
|
||||
break;
|
||||
case ASF.EUserInputType.Password:
|
||||
BotConfig.DecryptedSteamPassword = inputValue;
|
||||
BotConfig.SetDecryptedSteamPassword(inputValue, true);
|
||||
BotConfig.IsSteamPasswordSet = false;
|
||||
|
||||
break;
|
||||
|
@ -2033,7 +2033,10 @@ public sealed class Bot : IAsyncDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
if (requiresPassword && string.IsNullOrEmpty(BotConfig.DecryptedSteamPassword)) {
|
||||
if (requiresPassword) {
|
||||
string? decryptedSteamPassword = await BotConfig.GetDecryptedSteamPassword().ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(decryptedSteamPassword)) {
|
||||
RequiredInput = ASF.EUserInputType.Password;
|
||||
|
||||
string? steamPassword = await Logging.GetUserInput(ASF.EUserInputType.Password, BotName).ConfigureAwait(false);
|
||||
|
@ -2045,6 +2048,7 @@ public sealed class Bot : IAsyncDisposable {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2256,8 +2260,8 @@ public sealed class Bot : IAsyncDisposable {
|
|||
|
||||
// Decrypt login key if needed
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
if (!string.IsNullOrEmpty(loginKey) && (loginKey!.Length > 19) && (BotConfig.PasswordFormat != ArchiCryptoHelper.ECryptoMethod.PlainText)) {
|
||||
loginKey = ArchiCryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey);
|
||||
if (!string.IsNullOrEmpty(loginKey) && (loginKey!.Length > 19) && BotConfig.PasswordFormat.HasTransformation()) {
|
||||
loginKey = await ArchiCryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey).ConfigureAwait(false);
|
||||
}
|
||||
} else {
|
||||
// If we're not using login keys, ensure we don't have any saved
|
||||
|
@ -2287,7 +2291,7 @@ public sealed class Bot : IAsyncDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
string? password = BotConfig.DecryptedSteamPassword;
|
||||
string? password = await BotConfig.GetDecryptedSteamPassword().ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(password)) {
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
|
|
|
@ -273,37 +273,6 @@ public sealed class BotConfig {
|
|||
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 IsSteamParentalCodeSet { 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
@ -575,7 +564,9 @@ public sealed class BotConfig {
|
|||
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" };
|
||||
|
||||
if (!string.IsNullOrEmpty(botConfig.SteamLogin)) {
|
||||
|
@ -584,7 +575,8 @@ public sealed class BotConfig {
|
|||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
None,
|
||||
FamilySharing,
|
||||
|
|
Loading…
Reference in a new issue