Some Windows APIs return a primary token and some return an impersonation token. Some APIs require a primary token while others require an impersonation token.
For example, LogonUser
usually returns a primary token, except when using LOGON32_LOGON_NETWORK
as the logon type (dwLogonType
):
In most cases, the returned handle is a primary token that you can use in calls to the CreateProcessAsUser function. However, if you specify the LOGON32_LOGON_NETWORK flag, LogonUser returns an impersonation token that you cannot use in CreateProcessAsUser unless you call DuplicateTokenEx to convert it to a primary token.
SetThreadToken
requires an impersonation token while ImpersonateLoggedOnUser
which seems to do pretty much the same thing takes either one.
CreateProcessAsUser
and CreateProcessWithTokenW
both require a primary token and both note a primary token can be acquired from an impersonation token by calling DuplicateTokenEx
, but what do the token types mean?
The glossary says the following:
An access token contains the security information for a logon session. The system creates an access token when a user logs on, and every process executed on behalf of the user has a copy of the token. The token identifies the user, the user's groups, and the user's privileges. The system uses the token to control access to securable objects and to control the ability of the user to perform various system-related operations on the local computer. There are two kinds of access token, primary and impersonation.
An access token that is typically created only by the Windows kernel. It may be assigned to a process to represent the default security information for that process.
An access token that has been created to capture the security information of a client process, allowing a server to "impersonate" the client process in security operations.
But that's not entirely useful. It seems like somebody wanted to use big boy words like "kernel" but this only serves to raise more questions such as what else (besides being assigned to a process) can a primary token be used for and who else besides the kernel can create access tokens?
(Do they mean it the the Microsoft sense where the Kernel is only part of what runs in kernel-mode and there's also the Executive etc. or do they mean that user-mode code can also create tokens? Regardless, even if a user-mode code can create tokens it will have to do it through a system-call, as with any Object Manager object, so the token will actually be created in kernel mode anyway.)
Anyway, this doesn't answer the fundamental question: What the difference between the token types? Not what they may be used for or how they are usually created.
A friend referred me to Programming Windows Security by Keith Brown, which answers this question exactly.
Primary tokens can and should be called process tokens, and impersonation tokens can and should be called thread tokens. Primary tokens can only be attached to processes, and impersonation tokens can only be attached to threads. That's all. They can indeed be freely converted using DuplicateTokenEx
(assuming you have the necessary access rights to the handle you wish to convert, obviously).
From page 115 in the book:
BOOL DuplicateTokenEx( HANDLE ExistingToken, // in DWORD DesiredAccess, // in LPSECURITY_ATTRIBUTES Attributes, // in, optional SECURITY_IMPERSONATION_LEVEL ImpLevel, // in TOKEN_TYPE Type, // in PHANDLE NewToken); // out
...
The
Type
parameter is a historical artifact. If you look at the definition of theTOKEN_TYPE
enumeration, you'll find that tokens have been taxonomized into two categories: impersonation versus primary tokens. Don't get hung up on this nomenclature; the meaning is actually much simpler than it sounds. Impersonation tokens can only be attached to threads, and primary tokens can only be attached to processes. That's all it means. The process token obtained earlier viaOpenProcessToken
was therefore a primary token.In very early versions of Windows NT (3.x), there were much more severe restrictions on what you could do with a token, depending on where you originally got it from, and hence the token type was introduced to track the intended usage of the token. Because this text assumes you're using Windows NT 4.0 or greater, just think of an impersonation token as a "thread token", and a primary token as a "process token", and use
DuplicateTokenEx
to convert between the two whenever necessary. Windows NT 4.0 tore down the boundaries between the two by introducingDuplicateTokenEx
; the Windows NT 3.x version of this function,DuplicateToken
, was hardcoded to only produce impersonation tokens. In fact, now you should be able to see the silly bug that causes the first call toSetThreadToken
to fail: the code is attempting to attach a primary token (the one obtained from the process) to a thread (which requires an impersonation token). That's a no-no. To fix both the logical problem and the silly historical problem, here's the corrected code:
Other stuff, which aren't strictly an answer to the question, but were mentioned in the question:
ImpersonateLoggedOnUser
goes the extra mile and converts primary tokens to impersonation tokens, while SetThreadToken
doesn't bother. Why? Who knows? Probably for the same reason some APIs enable privileges for the duration of the call, while others require callers to enable the privileges on their own.LogonUser
(and LsaLogonUser
) probably return impersonation tokens for network logons, because of the assumption of who usually perform network logons (p. 83 onwards, for example).ZwCreateToken
, which requires pretty unusual privileges (specifically, the unique SE_CREATE_TOKEN_NAME
privilege). You probably shouldn't...