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");
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();