I want to use FileInfo and CopyTo to move some files across a network. I have to move the files to a share on a server that has to be accessed using a particular user account. How do I do this - do I have to impersonate that user and then do the copy?
I am using .net 4 and was wondering what the best way accomplish the impersonation is. I've read about using pInvoke and using advapi32.dll, but I was hoping someone could recommend a better way to do this.
Thanks for any thoughts.
EDIT
Thanks for the replies. This is not a service, it is a console app, but it will be run from multiple machines. Is there any advantage to using a mapping over using impersonation, or vice-versa? what is the recommended approach?
I have also considered using a batch file to create the mappings and do the copy, but I wasn't sure how easily it would be to accomplish because the folders to copy from will not always be the same - it will always be within one directory, but the subdirectory name changes.
You don't need impersonation all you have to do is to establish a file mapping using the credentials you want to use. You can accomplish this using either net use as a shell out command or WNetAddConnection2
If you are running as a service, you may need to impersonate. This is not the complete code but the gist of it:
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern unsafe int FormatMessage(int dwFlags, ref IntPtr lpSource, int dwMessageId, int dwLanguageId, ref string lpBuffer, int nSize, IntPtr* arguments);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);
IntPtr token = IntPtr.Zero;
bool isSuccess = LogonUser(username, domain, password, impersonationType, Logon32ProviderDefault, ref token);
if (!isSuccess)
{
RaiseLastError();
}
WindowsIdentity newIdentity = new WindowsIdentity(token);
WindowsImpersonationContext impersonatedUser = newIdentity.Impersonate();
Save the token and then later
CloseHandle(token);
Related
I have a C# program that require SeSystemEnvironmentPrivilege to access the UEFI NVRAM.
I found a really long code, that uses Win32 API to get the privilege, but is there a .NET version to get it? In process class, or somewhere else?
If it is really necessary you can use the AdjustTokenPrivileges function.
Something like this:
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
You can get more info here:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa446619%28v=vs.85%29.aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/aa375728(v=vs.85).aspx#privilege_constants
I'm trying to determine the file name of a certificate's private key stored on a remote Windows (2k3/2k8) machine and am having some difficulty. I'm also not that familiar with Microsoft's CryptAPI, so I'm looking for any help you can provide.
The purpose of this exercise is to find certificates with private keys installed on a remote computer that meet a specific criteria and ensure the correct rights are assigned to their private key files. Although I could assign rights at the folder level, I'd prefer to only assign rights at the private key file level where it's necessary (for obvious reasons).
Here's the scenario, assume a service account with administrative-like permissions is accessing the certificate store:
I retreive the remote certificate store using the following call from C# using p/invoke:
[DllImport("CRYPT32", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertOpenStore(int storeProvider, int encodingType, int hcryptProv, int flags, string pvPara);
IntPtr storeHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM,
0,
0,
CERT_SYSTEM_STORE_LOCAL_MACHINE,
string.Format(#"\{0}{1}", serverName, name));
I then use CertEnumCertificatesInStore to retreive certificates that I want to evaluate.
[DllImport("CRYPT32", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertEnumCertificatesInStore(IntPtr storeProvider, IntPtr prevCertContext);
IntPtr certCtx = IntPtr.Zero;
certCtx = CertEnumCertificatesInStore(storeHandle, certCtx);
If a certificate matches my criteria, I create an X509Certificate2 instance from the IntPtr returned from the CertEnumCertificatesInStore call like:
X509Certificate2 current = new X509Certificate2(certCtx);
Once I have the X509Certificate2 instances for certificates I'm interested in, I call CryptAcquireCertificatePrivateKey to get the private key provider:
[DllImport("crypt32", CharSet = CharSet.Unicode, SetLastError = true)]
internal extern static bool CryptAcquireCertificatePrivateKey(IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pdwKeySpec, ref bool pfCallerFreeProv);
//cert is an X509Certificate2
CryptAcquireCertificatePrivateKey(cert.Handle,
0,
IntPtr.Zero,
ref hProvider,
ref _keyNumber,
ref freeProvider);
To retreive the private key file name, I try to request the Unique Container Name from the hProvider as pData like:
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
internal extern static bool CryptGetProvParam(IntPtr hCryptProv, CryptGetProvParamType dwParam, IntPtr pvData, ref int pcbData, uint dwFlags);
IntPtr pData = IntPtr.Zero;
CryptGetProvParam(hProvider, PP_UNIQUE_CONTAINER, pData, ref cbBytes, 0));
So far all of the above steps work great locally (servername == local machine name); however, the unique container name (private key filename) that's returned for a certificate stored in a remote computer's local machine certificate store doesn't render as the actual private key filename I'm seeing under:
w2k3: \Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys
ws08: \ProgramData\Microsoft\Crypto\RSA\MachineKeys
For example, if I run the above steps directly on the remote machine, I get a private key file name AAAAAAA-111111, but if I run them remotely, I get a private key BBBBBBBB-2222222. Also, if I install the remote certificate locally and run the steps against my local machine I get the same private key name BBBBBBBB-2222222.
Most likely I feel that I may be missing a caveat in step 4, calling CryptAcquireCertificatePrivateKey. It may be that this call relies on the local machine's identity to generate the name of the unique container that would be used to store the private key blob.
Updated
After some further research, I found a blog that details out exactly how the filenames for private key containers are created here.
Instead of using CryptAcquireCertificatePrivateKey, you can use the methods described on that blog to get the private key container name on any machine once you have the name of the container obtained by CertGetCertificateContextProperty. The code here shows how to get the private key container name so you can generate the private key filename. * disclaimer - I'm pretty sure this is subject to change and may not even be complete, but am posting it in case it helps someone else in the future *
Structs and P/Invoke:
[StructLayout(LayoutKind.Sequential)]
public struct CryptKeyProviderInfo
{
[MarshalAs(UnmanagedType.LPWStr)]
public String pwszContainerName;
[MarshalAs(UnmanagedType.LPWStr)]
public String pwszProvName;
public uint dwProvType;
public uint dwFlags;
public uint cProvParam;
public IntPtr rgProvParam;
public uint dwKeySpec;
}
public const uint CERT_KEY_PROV_INFO_PROP_ID = 0x00000002;
[DllImport("crypt32.dll", SetLastError = true)]
internal extern static bool CertGetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, IntPtr pvData, ref uint pcbData);
IntPtr providerInfo = IntPtr.Zero;
string containerName = string.Empty;
try
{
//Win32 call w/IntPtr.Zero will get the size of our Cert_Key_Prov_Info_Prop_ID struct
uint pcbProviderInfo = 0;
if (!Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref pcbProviderInfo))
{
//if we can't get the certificate context, return string.empty
return string.Empty;
}
//Allocate heap for Cert_Key_Prov_Info_Prop_ID struct
providerInfo = Marshal.AllocHGlobal((int)pcbProviderInfo);
//Request actual Cert_Key_Prov_Info_Prop_ID struct with populated data using our allocated heap
if (Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, providerInfo, ref pcbProviderInfo))
{
//Cast returned pointer into managed structure so we can refer to it by it's structure layout
Win32.CryptKeyProviderInfo keyInfo = (Win32.CryptKeyProviderInfo)Marshal.PtrToStructure(providerInfo, typeof(Win32.CryptKeyProviderInfo));
//Get the container name
containerName = keyInfo.pwszContainerName;
}
//Do clean-up immediately if possible
if (providerInfo != IntPtr.Zero)
{
Marshal.FreeHGlobal(providerInfo);
providerInfo = IntPtr.Zero;
}
}
finally
{
//Do clean-up on finalizer if an exception cause early terminiation of try - after alloc, before cleanup
if (providerInfo != IntPtr.Zero)
Marshal.FreeHGlobal(providerInfo);
}
Using the CertGetCertificateContextProperty above, I was able to solve this question. So it is possible to determine the file name of a certificate's private key on a remote computer using the steps mentioned in the update.
I have a service running in Windows 7. In Windows 7 all services run in Session 0. From that service I want to create an interactive user session (in a session other than Session 0) and start an application in that session. My problem is that when I call LogonUser to start an interactive user session and then use CreateProcessAsUser to start the application the application ends up running in Session 0.
All of my code is C#.
Here is the relevant code:
[DllImport("advapi32.dll", SetLastError=true)]
static extern bool LogonUser(
string principal,
string authority,
string password,
UInt32 logonType,
UInt32 logonProvider,
out IntPtr token);
[DllImport("advapi32.dll", SetLastError=true)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
int dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);
IntPtr token;
LogonUser("UserName", ".", "Password",
LogonTypes.Interactive,LogonProviders.Default, out token)
<code to impersonate user>
string hd = Environment.ExpandEnvironmentVariables("%USERPROFILE%");
IntPtr envBlock = IntPtr.Zero;
CreateProcessAsUser(token, "PathToMenu.exe",
NORMAL_PRIORITY_CLASS |CREATE_UNICODE_ENVIRONMENT,
"WinSta0\\Default", hd, envBlock, "Menu");
Can anyone tell me what I'm doing wrong?
Tons of things can go wrong when trying to launch a process from a service in Vista/7. I would recommend that you start with this article and adapt it to your needs. I can tell you that I've used and modified the code in the article quite a bit, and it works. I'm sorry I can't show it to you because the modified code belongs to my company.
I have found some sample code on codeproject that allows for user impersonation.
This code works by importing the following unmanaged Win32 API functions:
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(IntPtr hToken,int impersonationLevel,ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
These functions are used to impersonate the target user, then perform some operations, then revert the impersonation context. Impersonating the user is achieved like so:
if ( LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE,LOGON32_PROVIDER_DEFAULT, ref token ) != 0 )
{
if ( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
{
tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
impersonationContext = tempWindowsIdentity.Impersonate();
}
}
I'm trying to understand why this code first gets the required token using LogonUser, then duplicates that token, before performing the impersonation on the duplicated token. Why not just impersonate using the token that you get from the LogonUser method.
Obviously the person that wrote this article understands this better than I do so it would appear that I am missing something. Could I please get an explanation of why the seemingly redundant token duplication step of this process is required?
As far as I know, token, passed to WindowsIdentity ctor should be an impersonation token. So, the author of that code using
DuplicateToken( token, 2, ref tokenDuplicate )
to create an impersonation token from primary token, returned by LogonUser(). That '2' magic number stands for SecurityImpersonation member of SECURITY_IMPERSONATION_LEVEL enum.
Links:
http://msdn.microsoft.com/en-us/library/aa378184%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/aa379572%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/aa446616%28v=vs.85%29.aspx
I used the common impersonation code and it worked just fine, until I inserted random 'dggdgsdg' in domain - and it worked nonetheless...
if (LogonUser(Username, Domain, Password, Logon32LogonInteractive, Logon32ProviderDefault, ref existingTokenHandle) &&
DuplicateToken(existingTokenHandle, (int)SecurityImpersonationLevel.SecurityDelegation, ref duplicateTokenHandle))
{
Identity = new WindowsIdentity(duplicateTokenHandle);
ImpersonationContext = Identity.Impersonate();
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
I used some TestUser on my domain, and it worked. I then switched domain, to random nonsense 'werwerhrg', and it impersonated the TestUser on my domain! Why? I would expect an exception to be thrown, why on earth is it working?
private const int Logon32LogonInteractive = 2;
private const int Logon32ProviderDefault = 0;
public enum SecurityImpersonationLevel
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private extern static bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DuplicateToken(IntPtr existingTokenHandle, int securityImpersonationLevel, ref IntPtr duplicateTokenHandle);
I believe the answer lies in how authentication is performed. LogonUser will attempt to log you on to the local computer it was executed on. If this computer is on a domain, then your password will be checked against an AD controller and be verified.
If you however provide a domain it cannot find it will authenticate against it's own local userbase as a fallback. Locally it will use NTLM (NTLM is used when client and server are the same machine). NTLM verifies password hashes and the username, and seems to not bother with the domain name (ref doc).
If you use UPN format instead and set domain to null then you will get an error if the domain is non-existant, and get your wanted result.
This is similar as if I create a user A with password B on two machines, then those users can access each others machines with the local rights without having to log on.
And all this is a good reason why you should stay away from local accounts in a domain world (at least with the same usernames, prefix them), and why non-domain machines should not be able to see each other on a network. And why you should use Kerberos all over if you can instead of NTLM.