diff --git a/Pal.Client/Configuration/AccountConfigurationV7.cs b/Pal.Client/Configuration/AccountConfigurationV7.cs index 5082463..de28185 100644 --- a/Pal.Client/Configuration/AccountConfigurationV7.cs +++ b/Pal.Client/Configuration/AccountConfigurationV7.cs @@ -9,6 +9,8 @@ namespace Pal.Client.Configuration { public class AccountConfigurationV7 : IAccountConfiguration { + private const int EntropyLength = 16; + [JsonConstructor] public AccountConfigurationV7() { @@ -30,11 +32,7 @@ namespace Pal.Client.Configuration EncryptedId = accountId.Substring(2); Entropy = ConfigurationData.FixedV1Entropy; Format = EFormat.UseProtectedData; - - // try to migrate away from v1 entropy if possible - Guid? decryptedId = DecryptAccountId(); - if (decryptedId != null) - (EncryptedId, Entropy, Format) = EncryptAccountId(decryptedId.Value); + EncryptIfNeeded(); } else if (Guid.TryParse(accountId, out Guid guid)) (EncryptedId, Entropy, Format) = EncryptAccountId(guid); @@ -65,7 +63,7 @@ namespace Pal.Client.Configuration private Guid? DecryptAccountId() { - if (Format == EFormat.UseProtectedData) + if (Format == EFormat.UseProtectedData && ConfigurationData.SupportsDpapi) { try { @@ -80,24 +78,31 @@ namespace Pal.Client.Configuration } else if (Format == EFormat.Unencrypted) return Guid.Parse(EncryptedId); + else if (Format == EFormat.ProtectedDataUnsupported && !ConfigurationData.SupportsDpapi) + return Guid.Parse(EncryptedId); else return null; } private (string encryptedId, byte[]? entropy, EFormat format) EncryptAccountId(Guid g) { - try + if (!ConfigurationData.SupportsDpapi) + return (g.ToString(), null, EFormat.ProtectedDataUnsupported); + else { - byte[] entropy = RandomNumberGenerator.GetBytes(16); - byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), entropy, DataProtectionScope.CurrentUser); - return (Convert.ToBase64String(guidBytes), entropy, EFormat.UseProtectedData); - } - catch (Exception) - { - return (g.ToString(), null, EFormat.Unencrypted); + try + { + byte[] entropy = RandomNumberGenerator.GetBytes(EntropyLength); + byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), entropy, DataProtectionScope.CurrentUser); + return (Convert.ToBase64String(guidBytes), entropy, EFormat.UseProtectedData); + } + catch (Exception) + { + return (g.ToString(), null, EFormat.Unencrypted); + } } } - + public bool EncryptIfNeeded() { if (Format == EFormat.Unencrypted) @@ -111,9 +116,7 @@ namespace Pal.Client.Configuration return true; } } - -#pragma warning disable CS0618 // Type or member is obsolete - if (Format == EFormat.UseProtectedData && ConfigurationData.FixedV1Entropy.SequenceEqual(Entropy ?? Array.Empty())) + else if (Format == EFormat.UseProtectedData && Entropy is { Length: < EntropyLength }) { Guid? g = DecryptAccountId(); if (g != null) @@ -122,7 +125,6 @@ namespace Pal.Client.Configuration return true; } } -#pragma warning restore CS0618 // Type or member is obsolete return false; } @@ -131,6 +133,12 @@ namespace Pal.Client.Configuration { Unencrypted = 1, UseProtectedData = 2, + + /// + /// Used for filtering: We don't want to overwrite any entries of this value using DPAPI, ever. + /// This is mostly a wine fallback. + /// + ProtectedDataUnsupported = 3, } } } diff --git a/Pal.Client/Configuration/ConfigurationData.cs b/Pal.Client/Configuration/ConfigurationData.cs index e8cb169..c93339c 100644 --- a/Pal.Client/Configuration/ConfigurationData.cs +++ b/Pal.Client/Configuration/ConfigurationData.cs @@ -1,4 +1,7 @@ -using System; +using Dalamud.Logging; +using System; +using System.Linq; +using System.Security.Cryptography; namespace Pal.Client.Configuration { @@ -6,5 +9,31 @@ namespace Pal.Client.Configuration { [Obsolete("for V1 import")] internal static readonly byte[] FixedV1Entropy = { 0x22, 0x4b, 0xe7, 0x21, 0x44, 0x83, 0x69, 0x55, 0x80, 0x38 }; + + private static bool? _supportsDpapi = null; + public static bool SupportsDpapi + { + get + { + if (_supportsDpapi == null) + { + try + { + byte[] input = RandomNumberGenerator.GetBytes(32); + byte[] entropy = RandomNumberGenerator.GetBytes(16); + byte[] temp = ProtectedData.Protect(input, entropy, DataProtectionScope.CurrentUser); + byte[] output = ProtectedData.Unprotect(temp, entropy, DataProtectionScope.CurrentUser); + _supportsDpapi = input.SequenceEqual(output); + } + catch (Exception) + { + _supportsDpapi = false; + } + + PluginLog.Verbose($"DPAPI support: {_supportsDpapi}"); + } + return _supportsDpapi.Value; + } + } } }