// AccessToken.h
#pragma once
#pragma unmanaged
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define _WIN32_WINNT 0x500
#pragma comment (lib, "advapi32")
#include<cstddef>
#include<string>
#include<sstream>
#include<tchar.h>
#include<windows.h>
#include<sddl.h>
#pragma managed
#include<vcclr.h>
using namespace System;
using namespace System::Runtime::InteropServices;
namespace AccessToken {
/* First of all, reinvent the wheel */
public enum class TOKEN_TYPEEnum {
TokenPrimary = 1,
TokenImpersonation
} ;
//public enum class TokenInfo {
// TokenUser = 1,
// TokenGroups,
// TokenPrivileges,
// TokenOwner,
// TokenPrimaryGroup,
// TokenDefaultDacl,
// TokenSource,
// TokenType,
// TokenImpersonationLevel,
// TokenStatistics,
// TokenRestrictedSids,
// TokenSessionId,
// TokenGroupsAndPrivileges,
// TokenSessionReference,
// TokenSandBoxInert,
// TokenAuditPolicy,
// TokenOrigin,
// MaxTokenInfoClass /* MaxTokenInfoClass should always be the last enum */
//};
#pragma unmanaged
__int64 LiToI64(const LARGE_INTEGER &OldClass)
{
__int64 Result = OldClass.LowPart;
Result += __int64(OldClass.HighPart) << 32;
return Result;
}
#pragma managed
public ref class ManagedTokenHandle : public Microsoft::Win32::SafeHandles::SafeHandleZeroOrMinusOneIsInvalid
{/* adapter class that enables us to get the handle from SafeHandleZeroOrMinusOneIsInvalid */
public:
ManagedTokenHandle(IntPtr handleIn) : Microsoft::Win32::SafeHandles::SafeHandleZeroOrMinusOneIsInvalid(true)
{
this->HandleInternal = handleIn;
};
ManagedTokenHandle(int handleIn) : Microsoft::Win32::SafeHandles::SafeHandleZeroOrMinusOneIsInvalid(true)
{
this->HandleInternal = IntPtr(handleIn);
};
~ManagedTokenHandle()
{
this->ReleaseHandle();
};
property IntPtr HandleInternal
{
IntPtr get()
{
return this->handle;
};
void set(IntPtr value)
{
if(value != this->handle)
{
this->ReleaseHandle();
this->SetHandle(value);
}
};
}
virtual bool ReleaseHandle() override
{
bool Result = (::CloseHandle(this->HandleInternal.ToPointer()) != 0);
this->SetHandleAsInvalid();
return Result;
};
};
/** This class is based on the data from "The Windows Access Control Model - Part 2",
* and modelled after the ATL class library.
* Error codes have been shunned for Exception types.
**/
public ref class AccessToken : System::Security::Principal::WindowsIdentity
{
private:
/// <summary>Override the base member... we need a setter for it.
/// </summary>
ManagedTokenHandle ^TokenHandle;
/// <summary>Helper function to manage the GetTokenInformation function
/// and handle any conversions
/// </summary>
///
/// <param name="TokenClass">Which data you want returned from the token.</param>
array<unsigned char> ^GetTokenStuff(TOKEN_INFORMATION_CLASS TokenClass)
{
/* Frontend for GetTokenInformation */
DWORD dwLength = 0, dwNeededLength = 0;
if(this->Token.ToPointer() == NULL)
{
throw gcnew System::NullReferenceException(_T("Class not initialized"));
}
::GetTokenInformation(this->Token.ToPointer(), static_cast<TOKEN_INFORMATION_CLASS>(static_cast<int>(TokenClass)), NULL,
dwLength, &dwNeededLength);
/* We expect this to fail. */
array<unsigned char> ^tokBuf = gcnew array<unsigned char> (dwNeededLength);
{
pin_ptr<unsigned char> tokPin = &tokBuf[0];
dwLength = dwNeededLength;
if(!::GetTokenInformation(this->Token.ToPointer(), static_cast<TOKEN_INFORMATION_CLASS>(static_cast<int>(TokenClass)), tokPin, dwLength, &dwNeededLength))
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
}
return tokBuf;
};
public:
AccessToken(IntPtr userToken)
: System::Security::Principal::WindowsIdentity(userToken), TokenHandle(gcnew ManagedTokenHandle(userToken))
{
Token = userToken;
};
AccessToken(IntPtr userToken, String ^type)
: System::Security::Principal::WindowsIdentity(userToken, type)
{
Token = userToken;
};
/// <summary>Constructs the</summary>
/// <param name="userToken">The token that this class will associate with (obtain using P/Invoke or
/// static GetAccessToken)</param>
/// <param name="type">All other parameters are for WindowsIdentity</param>
AccessToken(IntPtr userToken, String ^type, System::Security::Principal::WindowsAccountType acctType)
: System::Security::Principal::WindowsIdentity(userToken, type, acctType)
{
Token = userToken;
};
/// <summary>Opens an access token, using the specified WindowsIdentity.
/// By default, the current process token is opened.
/// </summary>
/// <param name="userToken">The token to the user process.</param>
/// <param name="type">WindowsAuthentication or custom authentication</param>
/// <param name="acctType"></param>
/// <param name="isAuthenticated"></param>
AccessToken(IntPtr userToken, String ^type, System::Security::Principal::WindowsAccountType acctType,
bool isAuthenticated) : System::Security::Principal::WindowsIdentity
(userToken, type, acctType, isAuthenticated) {};
/// <summary>frees up resources</summary>
~AccessToken() {};
/* Default parameter workaraounds */
/// <summary>See GetProcessToken(full parameters)</summary>
void GetProcessToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess)
{
this->GetProcessToken(dwDesiredAccess, IntPtr(0));
};
void GetThreadToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess)
{
this->GetThreadToken(dwDesiredAccess, IntPtr(NULL));
};
void GetThreadToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess, IntPtr(hThread))
{
this->GetThreadToken(dwDesiredAccess, hThread, true);
};
void OpenThreadToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess)
{
this->OpenThreadToken(dwDesiredAccess, false);
};
void OpenThreadToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess, bool bImpersonate)
{
this->OpenThreadToken(dwDesiredAccess, bImpersonate, true);
};
void OpenThreadToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess, bool bImpersonate, bool bOpenAsSelf)
{
this->OpenThreadToken(dwDesiredAccess, bImpersonate, true, System::Security::Principal::TokenImpersonationLevel::Impersonation);
};
static ManagedTokenHandle ^GetAccessToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess)
{
return GetAccessToken(IntPtr(0), dwDesiredAccess);
};
/// <summary>If you need a token, obtain it using this function</summary>
/// <param name="processHandle">An optional handle to the process for which you want an access token.</param>
/// <param name="dwDesiredAccess">The requested rights to the handle</param>
static ManagedTokenHandle ^GetAccessToken(IntPtr processHandle, System::Security::Principal::TokenAccessLevels dwDesiredAccess)
{
HANDLE procHandle = processHandle.ToPointer();
if(procHandle == NULL)
{
procHandle = ::GetCurrentProcess();
}
HANDLE hToken = NULL;
if(!::OpenProcessToken(procHandle, static_cast<DWORD>(dwDesiredAccess), &hToken))
{
throw gcnew System::Security::SecurityException(_T("Could not open process access token"));
}
return gcnew ManagedTokenHandle(IntPtr(hToken));
};
/// <summary>We need a setter for the Token function (to override its functionality).
/// </summary>
property IntPtr Token
{
IntPtr get() new
{
return this->TokenHandle->HandleInternal;
};
void set(IntPtr value)
{
this->TokenHandle->HandleInternal = value;
};
};
/* Implemented methods. */
/// <summary>Duplicates the ATL library function.
/// BUGBUG: this does not update the Token from WindowsIdentity
/// </summary>
void GetEffectiveToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess)
{
try
{
this->GetThreadToken(dwDesiredAccess);
}
catch (System::Security::SecurityException ^)
{
this->GetProcessToken(dwDesiredAccess);
}
};
void GetProcessToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess, IntPtr hProcess)
{
/* Stage the Access token before committing it */
HANDLE hToken = this->Token.ToPointer();
if(hProcess.ToPointer() == NULL)
{
hProcess = IntPtr(::GetCurrentProcess());
}
if(!::OpenProcessToken(hProcess.ToPointer(), static_cast<DWORD>(dwDesiredAccess), &hToken))
{
throw gcnew System::Security::SecurityException(_T("Could not open process access token"));
}
this->Token = IntPtr(hToken);
};
void OpenThreadToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess,
bool bImpersonate, bool bOpenAsSelf,
System::Security::Principal::TokenImpersonationLevel Impersonator)
{
/* Stage the Access token before committing it */
HANDLE hToken = this->Token.ToPointer();
SECURITY_IMPERSONATION_LEVEL sil = static_cast<SECURITY_IMPERSONATION_LEVEL>(Impersonator);
if(!::ImpersonateSelf(sil))
{
throw gcnew System::Security::SecurityException(_T("Could not set impersonation level for current thread"));
}
if(!::OpenThreadToken(::GetCurrentThread(), static_cast<DWORD>(dwDesiredAccess), bOpenAsSelf, &hToken))
{
throw gcnew System::Security::SecurityException(_T("Could not open thread access token"));
}
if(bImpersonate)
::RevertToSelf();
this->Token = IntPtr(hToken);
};
void GetThreadToken(System::Security::Principal::TokenAccessLevels dwDesiredAccess, IntPtr hThread, bool bOpenAsSelf)
{
/* Stage the Access token before committing it */
HANDLE hToken = this->Token.ToPointer();
if(hThread.ToPointer() == NULL)
{
hThread = IntPtr(::GetCurrentThread());
}
if(!::OpenThreadToken(hThread.ToPointer(), static_cast<DWORD>(dwDesiredAccess), bOpenAsSelf, &hToken))
{
throw gcnew System::Security::SecurityException(_T("Could not open thread access token"));
}
this->Token = IntPtr(hToken);
};
/// <summary>Enables or disables the specified privilege. Unnecessary in .NET
/// (The framework functions do this automatically). Don't forget to disable the
/// privilege once you've used it.</summary>
/// <param name="privilegeName">The privilege to enable</param>
/// <param name="bEnable">true to enable, false to disable.</param>
void SetPrivilege(System::String ^privilegeName, bool bEnable)
{/* Most of the .NET functions enable privileges automatically, so you don't need to do this. */
TOKEN_PRIVILEGES tp = {0};
LUID luid = {0};
{
pin_ptr<const wchar_t> strPin = ::PtrToStringChars(privilegeName);
const wchar_t *lpszPrivilege = strPin;
if( !::LookupPrivilegeValue(NULL, lpszPrivilege, &luid) )
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if(bEnable == TRUE) tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else tp.Privileges[0].Attributes = 0;
::SetLastError(ERROR_SUCCESS);
if( !::AdjustTokenPrivileges(this->Token.ToPointer(), FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)
|| ::GetLastError() != ERROR_SUCCESS)
{/* AdjustTokenPrivilege doesn't fail in case of non-admin. Just GetLastError() == ERROR_NOT_ALL_ASSIGNED. */
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
}
/* Due to marshalling, there is no advantage in implementing the other Privilege setters. */
/* Properties */
property System::Security::AccessControl::RawAcl ^DefaultDacl
{
System::Security::AccessControl::RawAcl ^get()
{
array<unsigned char> ^tokBuf = this->GetTokenStuff(TokenDefaultDacl);
pin_ptr<unsigned char> tokPin = &tokBuf[0];
TOKEN_DEFAULT_DACL *tp = reinterpret_cast<TOKEN_DEFAULT_DACL *>(tokPin);
/* It's easier to convert this into SDDL form, rather than relying on the way the structure is laid out. */
System::String ^sddlForm;
{
LPTSTR StringSD = NULL;
SECURITY_DESCRIPTOR ppSD = {0};
::InitializeSecurityDescriptor(&ppSD, SECURITY_DESCRIPTOR_REVISION);
::SetSecurityDescriptorDacl(&ppSD, TRUE, tp->DefaultDacl, TRUE);
::ConvertSecurityDescriptorToStringSecurityDescriptor(&ppSD, SDDL_REVISION_1,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION |
UNPROTECTED_DACL_SECURITY_INFORMATION, &StringSD, NULL);
sddlForm = gcnew System::String(StringSD);
::LocalFree(StringSD); StringSD = NULL;
}
System::Security::AccessControl::RawSecurityDescriptor ^ResultAcl =
gcnew System::Security::AccessControl::RawSecurityDescriptor(sddlForm);
return ResultAcl->DiscretionaryAcl;
};
void set(System::Security::AccessControl::RawAcl ^value)
{
array<unsigned char> ^AclBuf;
value->GetBinaryForm(AclBuf, 0);
pin_ptr<unsigned char> tokPin = &AclBuf[0];
PACL pDacl = reinterpret_cast<PACL>(tokPin);
TOKEN_DEFAULT_DACL tp = {pDacl};
if(!::SetTokenInformation(this->Token.ToPointer(),
static_cast<TOKEN_INFORMATION_CLASS>(static_cast<int>(TokenDefaultDacl)), &tp, sizeof(tp)))
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
};
};
property System::Security::Principal::SecurityIdentifier ^PrimaryGroup
{
System::Security::Principal::SecurityIdentifier ^get()
{
array<unsigned char> ^tokBuf = this->GetTokenStuff(TokenPrimaryGroup);
pin_ptr<unsigned char> tokPin = &tokBuf[0];
TOKEN_PRIMARY_GROUP *tp = reinterpret_cast<TOKEN_PRIMARY_GROUP *>(tokPin);
array<unsigned char> ^arr = gcnew array<unsigned char>(::GetLengthSid(tp->PrimaryGroup));
for(int i = 0; i < arr->Length; i++)
{
arr[i] = reinterpret_cast<unsigned char *>(tp->PrimaryGroup)[i];
}
return gcnew System::Security::Principal::SecurityIdentifier(arr, 0);
};
void set(System::Security::Principal::SecurityIdentifier ^value)
{
array<unsigned char> ^binaryForm;
value->GetBinaryForm(binaryForm, 0);
pin_ptr<unsigned char> aclPin = &binaryForm[0];
TOKEN_PRIMARY_GROUP tp = {reinterpret_cast<PSID>(aclPin)};
if(!::SetTokenInformation(this->Token.ToPointer(),
static_cast<TOKEN_INFORMATION_CLASS>(static_cast<int>(TokenPrimaryGroup)), &tp, sizeof(tp)))
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
}
};
System::Collections::Specialized::NameValueCollection ^GetPrivileges()
{
array<unsigned char> ^tokBuf = this->GetTokenStuff(TokenPrivileges);
pin_ptr<unsigned char> tokPin = &tokBuf[0];
TOKEN_PRIVILEGES *tp = reinterpret_cast<TOKEN_PRIVILEGES *>(tokPin);
System::Collections::Specialized::NameValueCollection ^PrivList = gcnew System::Collections::Specialized::NameValueCollection (tp->PrivilegeCount);
for(size_t i = 0; i < tp->PrivilegeCount; i++)
{
TCHAR *lpszPrivilege = NULL;
DWORD cchName = 0;
::LookupPrivilegeName(NULL, &tp->Privileges[i].Luid, NULL, &cchName);
cchName++;
try {
lpszPrivilege= new TCHAR[cchName];
if(!::LookupPrivilegeName(NULL, &tp->Privileges[i].Luid, lpszPrivilege, &cchName))
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
System::String ^sPrivilege = gcnew System::String(lpszPrivilege);
// System::Diagnostics::Debug::Write(tp->Privileges[i].Attributes.ToString());
PrivList->Add(sPrivilege, tp->Privileges[i].Attributes.ToString());
delete [] lpszPrivilege; lpszPrivilege = NULL;
} catch(...) {
if(lpszPrivilege != NULL)
delete [] lpszPrivilege;
lpszPrivilege = NULL;
throw;
}
}
return PrivList;
};
property TOKEN_TYPEEnum ^Type
{
TOKEN_TYPEEnum ^get()
{
array<unsigned char> ^tokBuf = this->GetTokenStuff(TokenType);
pin_ptr<unsigned char> tokPin = &tokBuf[0];
TOKEN_TYPE *tp = reinterpret_cast<TOKEN_TYPE *>(tokPin);
return TOKEN_TYPEEnum(*tp);
};
};
property Int64 LogonSessionId
{
Int64 get()
{
array<unsigned char> ^tokBuf = this->GetTokenStuff(TokenStatistics);
pin_ptr<unsigned char> tokPin = &tokBuf[0];
TOKEN_STATISTICS *tp = reinterpret_cast<TOKEN_STATISTICS *>(tokPin);
LARGE_INTEGER Result = {tp->AuthenticationId.LowPart, tp->AuthenticationId.HighPart};
return LiToI64(Result);
}
};
property unsigned long TerminalServicesSessionId
{
unsigned long get()
{
/* We can call GetTokenInformation() directly here, since DWORD is easily allocatable. */
DWORD dwLength = sizeof(DWORD), dwNeededLength = sizeof(DWORD), Answer = 0;
if(this->Token.ToPointer() == NULL)
{
throw gcnew System::NullReferenceException(_T("Class not initialized"));
}
if(!::GetTokenInformation(this->Token.ToPointer(), TokenSessionId, &Answer, dwLength,
&dwNeededLength))
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
return Answer;
};
};
/// <summary>Takes the class's access token, and creates an impersonation token from it. this.Token needs
/// to have been opened with the TokenDuplicate right.
/// </summary>
/// <param name="sil">the impersonation level for the new token.</param>
/// <returns>a handle to the new impersonation token. Excepts on failure.</returns>
ManagedTokenHandle ^CreateImpersonationToken(System::Security::Principal::TokenImpersonationLevel sil)
{
HANDLE TokTmp = NULL;
if(!::DuplicateToken(this->Token.ToPointer(), static_cast<SECURITY_IMPERSONATION_LEVEL>(sil),
&TokTmp))
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
return gcnew ManagedTokenHandle(IntPtr(TokTmp));
}
/// <summary>Implements the Win32 LogonUser API for .NET</summary>
static ManagedTokenHandle ^LogonUser(System::String ^Username, System::String ^Domain,
System::Security::SecureString ^Password, int dwLogonType, int dwLogonProvider)
{
/* Unmanage the strings. */
BOOL bResult = FALSE;
pin_ptr<const wchar_t> lpszUser = ::PtrToStringChars(Username);
pin_ptr<const wchar_t> szDomain = ::PtrToStringChars(Domain);
/* stage the access token */
HANDLE hToken = NULL;
/* Get the globalAlloc IntPtr from lpszPassword */
LPTSTR lpszPassword = reinterpret_cast<LPTSTR>(Marshal::SecureStringToGlobalAllocUnicode(Password).ToPointer());
/* PASSWORD IS NOW IN PLAINTEXT. */
bResult = ::LogonUser(lpszUser, szDomain, lpszPassword, dwLogonType, dwLogonProvider, &hToken);
Password->Dispose(); lpszPassword = NULL;
/* OK, the password is now erased from memory */
if(hToken == NULL)
{
throw gcnew System::ComponentModel::Win32Exception(::GetLastError());
}
return gcnew ManagedTokenHandle(IntPtr(hToken));
};
};
} /* namespace AccessToken */