Search code examples
c#authenticationwinapiencryption

C# Dataprotector Decrypt function doesn't work with Logon or TokenService


I have code that reads protected values from a config file and decrypts the values into strings. Then the code uses those values in Windows Logon and TokenService functions.

The code works fine when I debug it from my desktop but the values aren't being decrypted correctly when I deploy the code to the server. My desktop is windows 11 and the deployed server runs on windows Server 2016.

If I hard-code the decrypted config values directly into these two functions they work on the deployed server.

When I debug the code that decrypts the config values, the variables are identical to what I hard-coded.

What am I missing?

UPDATE - I found this error in the logs: Decryption failed: The key {a79dd801-99f7-4ca9-a0a2-ad571f9ad08f} was not found in the key ring.

public class EncryptionService : IEncryptionService
{
    private readonly IDataProtector _protector;
    private static readonly ILog _log = LogManager.GetLogger(typeof(EncryptionService));

    // Constructor to initialize the IDataProtector using dependency injection
    public EncryptionService(IDataProtectionProvider provider)
    {
        // unique string that ensures different protection policies for different purposes
        _protector = provider.CreateProtector("MyPurposeIs...");
    }

    // Method to encrypt plain text data
    public string EncryptData(string plainText)
    {
        return _protector.Protect(plainText);
    }

    // Method to decrypt the encrypted data
    public string DecryptData(string encryptedData)
    {
        try
        {
            return _protector.Unprotect(encryptedData);
        }
        catch (Exception ex)
        {
            // If decryption fails (e.g., data is tampered or invalid), handle the exception
            return $"Decryption failed: {ex.Message}";
        }
    }

}


public class Utilities : IUtilities
{
    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
    int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);
    public IConfiguration _config;

    const int LOGON32_PROVIDER_DEFAULT = 0;
    const int LOGON32_LOGON_NEW_CREDENTIALS = 9;

    private static readonly ILog _log = LogManager.GetLogger(typeof(Utilities));
    private readonly IEncryptionService _encryptionService;


public Utilities(IConfiguration configuration, IEncryptionService encryptionService)
{
    _config = configuration;
    _encryptionService = encryptionService;
}

//domain, username and password are read from a config file, decrypted, then passed into this function

    public string[]? ImpersonateAndGetFiles(string domain, string username, string password, string sourcePath, string fileSpec)
    {
        string[]? resultArray = null;
        try
        {
            SafeAccessTokenHandle? safeAccessTokenHandle;
        
        

            bool returnValue = LogonUser(username, domain, password,
                                     LOGON32_LOGON_NEW_CREDENTIALS,
                                     LOGON32_PROVIDER_DEFAULT,
                                     out safeAccessTokenHandle);

            if (false == returnValue)
            {

                int ret = Marshal.GetLastWin32Error();

                _log.Error($"LogonUser failed with error code : {ret}");
                return resultArray;
            }

            if (safeAccessTokenHandle is not null)
            {
                _log.Info("Logon Successful. Getting file list.");
                #pragma warning disable CA1416 // Validate platform compatibility
                WindowsIdentity.RunImpersonated(safeAccessTokenHandle, () =>
                {
                    resultArray = Directory.GetFiles(sourcePath, fileSpec);
                    return Task.CompletedTask;
                });
                #pragma warning restore CA1416 // Validate platform compatibility
            }

        }
        catch (Exception ex)
        {
            _log.Error($"Error: {ex}");
            return resultArray;
        }

        return resultArray;
    }
}

//code that uses the functions
 string? domain = _config["domainCL"];
 string? userName = _config["userNameCL"];
 string? password = _config["passwordCL"];
string? unEncryptedDomain = null;
string? unEncryptedUserName = null;
string? unEncryptedPassword = null;

unEncryptedDomain = _encryptionService.DecryptData(domain);
unEncryptedUserName = _encryptionService.DecryptData(userName);
unEncryptedPassword = _encryptionService.DecryptData(password);
fileList = _utils.ImpersonateAndGetFiles(unEncryptedDomain, unEncryptedUserName, unEncryptedPassword, folder, "*.xml");

Solution

  • I found the problem. There is a key created and stored when files are encrypted. When the application runs on the deployed server, it is running under a different account, which means the key is stored in a different location. I set the key to persist to a common file and then re-encrypted everything with the new key. Then I copied the key file to the folder specified in my program.cs and it works because the code now finds and uses the same key as the one I encrypted the settings with.

      var host = Host.CreateDefaultBuilder(args)
                 .ConfigureServices(services => 
                 {
                     services.AddSingleton(_config);
                     services.AddTransient<App>();
                     services.AddDataProtection()
                     .SetApplicationName("App")
                     .PersistKeysToFileSystem(new DirectoryInfo(@"C:\webservices\Keys\") );
                     services.AddTransient<IEncryptionService, EncryptionService>();
    
                 })
                 .Build();