![]() |
Show Changes |
![]() |
Edit |
![]() |
|
![]() |
Recent Changes |
![]() |
Subscriptions |
![]() |
Lost and Found |
![]() |
Find References |
![]() |
Rename |
| Search |
History
| 7/25/2004 2:16:45 PM |
![]() |
List all versions |
If you've got a handle to a Windows token for a user, you'll likely want to wrap it in a WindowsPrincipal object. This will simplify basic tasks like group lookup, where you can just call WindowsPrincipal.IsInRole. It will also allow you to hang the token from the Thread.CurrentPrincipal property, which can be quite convenient (WhatIsThreadDotCurrentPrincipal). At first glace it seems quite trivial to wrap a token, given the constructor on WindowsIdentity that takes a token handle.
// this approach is naïve!
WindowsPrincipal NaiveWrapToken(IntPtr token) {
WindowsIdentity id = new WindowsIdentity(token);
WindowsPrincipal p = new WindowsPrincipal(id);
return p;
}
Unfortunately, as I discussed in WhatIsWindowsIdentityAndWindowsPrincipal, the resulting WindowsIdentity won't behave like you might expect it to. For example, even if the token represents an authenticated user, if you look at the WindowsIdentity.IsAnonymous property, you'll find that it returns false. The other properties that start with Is won't function properly, and the AuthenticationType property will be hardcoded to NTLM.
To solve this problem, you should call the more complicated constructor.
namespace System.Security.Principal {
public class WindowsIdentity : IIdentity {
public WindowsIdentity(
IntPtr token,
string authnType,
WindowsAccountType accountType,
bool isAuthenticated);
// ...
}
}
With this constructor, you can say what the IsAuthenticated property, as well as IsSystem and friends, should return, but because some tokens represent unauthenticated users (WhatIsANullSession), you should first look inside the token for the NT Authority\Authenticated Users SID and make your decision based on its presence or absence. Because you're starting with a raw token, however, this means calling native Win32 functions, so I've provided a helper function that will wrap tokens for you. The function is shown in Figure 25.1, and is available in the downloadable library.
WindowsIdentity* Token::CreateWindowsIdentity(IntPtr token) {
HANDLE htok = token.ToPointer();
// one annoying failure mode we need to watch for
// is that CheckTokenMembership insists on being
// passed an impersonation token. IMHO this is broken.
HANDLE dup = _dupTokenIfPrimary(htok);
CloseHandleIfNonZero closeThis(dup);
if (dup) {
htok = dup;
}
// check for NT Authority\Authenticated Users
BOOL isAuthenticated;
if (!CheckTokenMembership(htok,
&WellKnownSIDs::AuthenticatedUsers,
&isAuthenticated)) {
throwWin32Exception(S"CheckTokenMembership");
}
// check for NT Authority\SYSTEM
BOOL isSystem;
if (!CheckTokenMembership(0,
&WellKnownSIDs::LocalSystem,
&isSystem)) {
throwWin32Exception(S"CheckTokenMembership");
}
WindowsAccountType accountType;
if (isSystem) {
accountType = WindowsAccountType::System;
}
else {
// check for BUILTIN\Guests
BOOL isGuest;
if (!CheckTokenMembership(0,
&WellKnownSIDs::Guests,
&isGuest)) {
throwWin32Exception(S"CheckTokenMembership");
}
if (isGuest) {
accountType = WindowsAccountType::Guest;
}
else if (!isAuthenticated) {
accountType = WindowsAccountType::Anonymous;
}
else {
accountType = WindowsAccountType::Normal;
}
}
// ask the token where it came from and use that as the
// authentication type
TOKEN_SOURCE source;
DWORD cb = sizeof source;
int n = (int)htok;
if (!GetTokenInformation(htok, TokenSource,
&source, cb, &cb)) {
throwWin32Exception(S"GetTokenInformation");
}
// now we have enough information to create the wrapper
return new WindowsIdentity(htok,
new String(source.SourceName, 0,
sizeof(source.SourceName)),
accountType, isAuthenticated ? true : false);
}
Figure 25.1 Wrapping a token intelligently in Managed C++
If the code in the figure seems more complicated than it needs to be, well, welcome to the world of programming security via Win32. No wonder my last book on the topic (Brown 2000) was almost 600 pages long. Fortunately, as the .NET Framework matures we'll be seeing more and more high-level support for security programming, such as the ACL support slated for version 2.0. Let me clarify a few things about this code.
First of all, whenever you check for the presence of a group in a raw token, you should do so via the Win32 function CheckTokenMembership. This function is rather finicky, however, it only accepts what's known as an "impersonation" token. You see, impersonation tokens are designed to be used on a thread for impersonation (see WhatIsImpersonation) whereas the other type, the "primary" token, is designed to be put on a process. If you look at your process token by calling WindowsIdentity.GetCurrent, you'll be looking at a primary token. But given that there is a Win32 function for converting between these token types, the difference between them for all practical purposes is historical. It's not clear why CheckTokenMembership even cares, but it does—thus the call to the helper function _dupTokenIfPrimary, which basically calls DuplicateTokenEx to produce an impersonation token if necessary. It's this kind of esoterica that makes working with Windows security nightmarish (but ensures job security for the few folks that know how to do it). Of course, if we end up duplicating the token, we have an extra handle that needs closing, so I use the good old C++ destructor mechanism to ensure that it’s closed before the function ends, by writing a little helper class called CloseHandleIfNonZero to do this cleanup when the function is done.
Next, I need to check for a couple of well-known SIDs (WhatIsASID). A long time ago I got tired of having to look up the values for these SIDs and construct them manually every time I needed them. That’s why you'll see my code referring to an unmanaged C++ class called WellKnownSIDs, another class I wrote as part of this helper library. I hope you find it useful. When you upgrade to version 2.0 of the .NET Framework, you’ll find the SecurityIdentifier class and the WellKnownSidType enumeration helpful as well.
Finally, I determine the AuthenticationType by asking the token where it came from via a call to GetTokenInformation. This tends to be more accurate than simply hardcoding "NTLM" all the time!
Keep in mind that the WindowsIdentity constructors that accept a token handle as input will first duplicate the token. This means two things: Any changes (such as enabling privileges, as I discuss in WhatIsAPrivilege) you make in one won't affect the other, and you’ll be responsible for closing the token handle that you provided to the WindowsIdentity constructor. Because the code in Figure 25.1 ultimately calls one of these constructors, the same rules apply if you call my helper function to wrap a token.
Keith's first book-in-a-wiki. If you would like to read the book online or order a physical copy to throw at annoying coworkers, surf to the HomePage. Please note that due to overwhelming wikispam, this particular wiki is no longer editable.
About FlexWiki.
Recent Topics