Config: Make DPAPI support optional

This commit is contained in:
Liza 2023-02-15 13:27:41 +01:00
parent 550fa92a53
commit d1cb7e08f2
2 changed files with 57 additions and 20 deletions

View File

@ -9,6 +9,8 @@ namespace Pal.Client.Configuration
{ {
public class AccountConfigurationV7 : IAccountConfiguration public class AccountConfigurationV7 : IAccountConfiguration
{ {
private const int EntropyLength = 16;
[JsonConstructor] [JsonConstructor]
public AccountConfigurationV7() public AccountConfigurationV7()
{ {
@ -30,11 +32,7 @@ namespace Pal.Client.Configuration
EncryptedId = accountId.Substring(2); EncryptedId = accountId.Substring(2);
Entropy = ConfigurationData.FixedV1Entropy; Entropy = ConfigurationData.FixedV1Entropy;
Format = EFormat.UseProtectedData; Format = EFormat.UseProtectedData;
EncryptIfNeeded();
// try to migrate away from v1 entropy if possible
Guid? decryptedId = DecryptAccountId();
if (decryptedId != null)
(EncryptedId, Entropy, Format) = EncryptAccountId(decryptedId.Value);
} }
else if (Guid.TryParse(accountId, out Guid guid)) else if (Guid.TryParse(accountId, out Guid guid))
(EncryptedId, Entropy, Format) = EncryptAccountId(guid); (EncryptedId, Entropy, Format) = EncryptAccountId(guid);
@ -65,7 +63,7 @@ namespace Pal.Client.Configuration
private Guid? DecryptAccountId() private Guid? DecryptAccountId()
{ {
if (Format == EFormat.UseProtectedData) if (Format == EFormat.UseProtectedData && ConfigurationData.SupportsDpapi)
{ {
try try
{ {
@ -80,21 +78,28 @@ namespace Pal.Client.Configuration
} }
else if (Format == EFormat.Unencrypted) else if (Format == EFormat.Unencrypted)
return Guid.Parse(EncryptedId); return Guid.Parse(EncryptedId);
else if (Format == EFormat.ProtectedDataUnsupported && !ConfigurationData.SupportsDpapi)
return Guid.Parse(EncryptedId);
else else
return null; return null;
} }
private (string encryptedId, byte[]? entropy, EFormat format) EncryptAccountId(Guid g) 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); try
byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), entropy, DataProtectionScope.CurrentUser); {
return (Convert.ToBase64String(guidBytes), entropy, EFormat.UseProtectedData); byte[] entropy = RandomNumberGenerator.GetBytes(EntropyLength);
} byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), entropy, DataProtectionScope.CurrentUser);
catch (Exception) return (Convert.ToBase64String(guidBytes), entropy, EFormat.UseProtectedData);
{ }
return (g.ToString(), null, EFormat.Unencrypted); catch (Exception)
{
return (g.ToString(), null, EFormat.Unencrypted);
}
} }
} }
@ -111,9 +116,7 @@ namespace Pal.Client.Configuration
return true; return true;
} }
} }
else if (Format == EFormat.UseProtectedData && Entropy is { Length: < EntropyLength })
#pragma warning disable CS0618 // Type or member is obsolete
if (Format == EFormat.UseProtectedData && ConfigurationData.FixedV1Entropy.SequenceEqual(Entropy ?? Array.Empty<byte>()))
{ {
Guid? g = DecryptAccountId(); Guid? g = DecryptAccountId();
if (g != null) if (g != null)
@ -122,7 +125,6 @@ namespace Pal.Client.Configuration
return true; return true;
} }
} }
#pragma warning restore CS0618 // Type or member is obsolete
return false; return false;
} }
@ -131,6 +133,12 @@ namespace Pal.Client.Configuration
{ {
Unencrypted = 1, Unencrypted = 1,
UseProtectedData = 2, UseProtectedData = 2,
/// <summary>
/// Used for filtering: We don't want to overwrite any entries of this value using DPAPI, ever.
/// This is mostly a wine fallback.
/// </summary>
ProtectedDataUnsupported = 3,
} }
} }
} }

View File

@ -1,4 +1,7 @@
using System; using Dalamud.Logging;
using System;
using System.Linq;
using System.Security.Cryptography;
namespace Pal.Client.Configuration namespace Pal.Client.Configuration
{ {
@ -6,5 +9,31 @@ namespace Pal.Client.Configuration
{ {
[Obsolete("for V1 import")] [Obsolete("for V1 import")]
internal static readonly byte[] FixedV1Entropy = { 0x22, 0x4b, 0xe7, 0x21, 0x44, 0x83, 0x69, 0x55, 0x80, 0x38 }; 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;
}
}
} }
} }