Click here to Skip to main content
15,892,697 members
Articles / Programming Languages / Visual Basic

The Windows Access Control Model Part 3

Rate me:
Please Sign up or sign in to vote.
4.80/5 (28 votes)
1 Jul 200525 min read 233.1K   5.2K   126  
In the third part of this series, we will take a tour of the new access control classes coming in .NET v2.0.
// 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 */

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Mr. Shah is a reclusive C++/C# developer lurking somewhere in the depths of the city of London. He learnt physics at Kings' College London and obtained a Master in Science there. Having earned an MCAD, he teeters on the brink of transitioning from C++ to C#, unsure of which language to jump to. Fortunately, he also knows how to use .NET interop to merge code between the two languages (which means he won't have to make the choice anytime soon).

His interests (apart from programming) are walking, football (the real one!), philosophy, history, retro-gaming, strategy gaming, and any good game in general.

He maintains a website / blog / FAQ / junk at shexec32.serveftp.net, where he places the best answers he's written to the questions you've asked. If you can find him, maybe you can hire Mr. Shah to help you with anything C++[/CLI]/C#/.NET related Smile | :) .

Comments and Discussions