I'm working on a program that is intended to impersonate another account and then decrypt a file using a private key from the impersonated account's keystore.
The problem is that although I'm able to open the impersonated account's certificate store and instantiate an X509Certificate2 object with the appropriate cert, when the program attempts to access the private key, I receive an exception:
System.Security.Cryptography.CryptographicException: The system cannot find the file specified...at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()
I was able to locate the impersonated user's private key in the file system using the FindPrivateKey.exe utility & found it located in C:\Users\TESTUSR\AppData\Roaming\Microsoft\Crypto\RSA... I used this article as a guide to ensure that my own account has access to the private key file. I definitely have access to the key file from an NTFS perspective as I can load it in notepad (although it's jiberish, obviously).
Quite by accident, I discovered that the program does work if I pre-load the impersonated account's local profile by using runas to, for instance, open a command prompt as that user.
Any advice would be greatly appreciated!
class Program
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
static void Main(string[] args)
{
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
SafeTokenHandle safeTokenHandle;
try
{
string username = "TESTUSR";
string domain = "CONTOSO";
string password = "P@ssw0rd";
// Call LogonUser to obtain a handle to an access token.
if (!LogonUser(username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeTokenHandle))
return;
if (safeTokenHandle == null)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
using (safeTokenHandle)
{
using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
{
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
string neededCertSN = "17396B080000000000A5";
X509Store my = new X509Store(StoreLocation.CurrentUser);
my.Open(OpenFlags.ReadOnly);
string result;
foreach (X509Certificate2 cert in my.Certificates)
{
if (Regex.IsMatch(neededCertSN, cert.SerialNumber))
{
result = DecryptFile(args[0], ".txt", (RSACryptoServiceProvider)cert.PrivateKey);
Console.WriteLine("Result:\r\n" + result);
return;
}
}
Console.WriteLine("Encryption certificate not found.");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("An exception occurred:\r\n" + ex.ToString());
}
}
static string DecryptFile(string cypherTextFile, string plainTextFileExtension, RSACryptoServiceProvider rsaPrivateKey)
{
string plainTextFile = string.Empty;
try
{
if (!File.Exists(cypherTextFile))
return "Cyphertext file not found";
// Create instance of AesManaged for
// symetric decryption of the data.
using (AesManaged aesManaged = new AesManaged())
{
aesManaged.KeySize = 256;
aesManaged.BlockSize = 128;
aesManaged.Mode = CipherMode.CBC;
// Create byte arrays to get the length of
// the encrypted key and IV.
// These values were stored as 4 bytes each
// at the beginning of the encrypted package.
byte[] LenK = new byte[4];
byte[] LenIV = new byte[4];
// Consruct the file name for the decrypted file.
if (plainTextFileExtension[0] != '.')
plainTextFileExtension = "." + plainTextFileExtension;
string[] parts = cypherTextFile.Split('.');
for (int x = 0; x < parts.Length - 1; x++)
plainTextFile = plainTextFile + parts[x];
plainTextFile = plainTextFile + plainTextFileExtension;
// Use FileStream objects to read the encrypted
// file (inFs) and save the decrypted file (outFs).
using (FileStream inFs = new FileStream(cypherTextFile, FileMode.Open))
{
inFs.Seek(0, SeekOrigin.Begin);
inFs.Seek(0, SeekOrigin.Begin);
inFs.Read(LenK, 0, 3);
inFs.Seek(4, SeekOrigin.Begin);
inFs.Read(LenIV, 0, 3);
// Convert the lengths to integer values.
int lenK = BitConverter.ToInt32(LenK, 0);
int lenIV = BitConverter.ToInt32(LenIV, 0);
// Determine the start postition of
// the ciphter text (startC)
// and its length(lenC).
int startC = lenK + lenIV + 8;
int lenC = (int)inFs.Length - startC;
// Create the byte arrays for
// the encrypted AesManaged key,
// the IV, and the cipher text.
byte[] KeyEncrypted = new byte[lenK];
byte[] IV = new byte[lenIV];
// Extract the key and IV
// starting from index 8
// after the length values.
inFs.Seek(8, SeekOrigin.Begin);
inFs.Read(KeyEncrypted, 0, lenK);
inFs.Seek(8 + lenK, SeekOrigin.Begin);
inFs.Read(IV, 0, lenIV);
// Use RSACryptoServiceProvider
// to decrypt the AesManaged key.
byte[] KeyDecrypted = rsaPrivateKey.Decrypt(KeyEncrypted, false);
// Decrypt the key.
using (ICryptoTransform transform = aesManaged.CreateDecryptor(KeyDecrypted, IV))
{
// Decrypt the cipher text from
// from the FileSteam of the encrypted
// file (inFs) into the FileStream
// for the decrypted file (outFs).
using (FileStream outFs = new FileStream(plainTextFile, FileMode.Create))
{
int count = 0;
int offset = 0;
int blockSizeBytes = aesManaged.BlockSize / 8;
byte[] data = new byte[blockSizeBytes];
// By decrypting a chunk a time,
// you can save memory and
// accommodate large files.
// Start at the beginning
// of the cipher text.
inFs.Seek(startC, SeekOrigin.Begin);
using (CryptoStream outStreamDecrypted = new CryptoStream(outFs, transform, CryptoStreamMode.Write))
{
do
{
count = inFs.Read(data, 0, blockSizeBytes);
offset += count;
outStreamDecrypted.Write(data, 0, count);
}
while (count > 0);
outStreamDecrypted.FlushFinalBlock();
outStreamDecrypted.Close();
}
outFs.Close();
}
inFs.Close();
}
}
}
return plainTextFile;
}
catch (Exception e)
{
return "An exception occurred:\r\n" + e.ToString();
}
}
}
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{
}
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
The problem I'm having is that although I'm able to open the impersonated account's certificate store & grab the correct cert in a X509Certificate2 object, when the program tries to use the private key, it's
You need to manually load the users registry according to the CreateProcessAsUser
documentation:
CreateProcessAsUser does not load the specified user's profile into the HKEY_USERS registry key. Therefore, to access the information in the HKEY_CURRENT_USER registry key, you must load the user's profile information into HKEY_USERS with the LoadUserProfile function before calling CreateProcessAsUser.
RunAs would not change HKEY_CURRENT_USER in your process but it will cause the hive to be loaded under HKEY_USERS.
You could try calling LoadUserProfile
after LogonUser
...