Jump to navigation

Poupou's Corner of the Web

Looking for perfect security? Try a wireless brick.
Otherwise you may find some unperfect stuff here...

Weblog

Still sneak but no more previews

The source code for the WindowsIdentity, WindowsImpersonationContext and WindowsPrincipal classes are now on CVS.

This means that the previous samples should now work without problems on both Linux and Windows. I'd be interested to heard if this works on Solaris, MacOS X, FreeBSD or any other platform that Mono support.


4/19/2004 23:11:36 | Comments | Permalink

Strongswitching

According to Shawn Farkas using attributes for strongnaming assembly is coming to an end. While Whidbey will still allow using them, with a CS1699 warning, you'll be invited to use command-line arguments to the compiler.

  • /delaysign+ will replace [assembly: AssemblyDelaySign (true)]
  • /keyfile:file.snk will replace [assembly: AssemblyKeyFile ("file.snk")] and
  • /keycontainer:container will replace [assembly: AssemblyKeyName ("container")].

Yet this isn't completely new as the current Visual Basic compiler, vbc, already had those switches. So in that respect we could say that C# is just catching on ;-)

And Mono:: users shouldn't worry as the /keyfile, /keycontainer and /delaysign options are already part of MCS (post 0.31) but the CS1699 warning isn't - yet.

As for me I never like using attributes for strongnaming so I guess I won't cry over them...


4/15/2004 22:06:05 | Comments | Permalink

Sneak preview sequel : the Principal

Because of OS identities restrictions, mainly how we create them, my previous blog sample wasn't portable (i.e. limited to Linux and other Unix operating system). Thankfully things are easier when dealing with principals.

The IPrincipal interface has one property, Identity, and one method, bool IsInRole(string). The WindowsPrincipal implementation provides overloaded IsInRole methods but this just adds other ways to get the same results.

The goal of classes implementing IPrincipal is to allow a consistent way to use Role Based Access Control (RBAC). The GenericPrincipal let you supply an array of string, in the constructor, with the roles you want to associate with the identity. The WindowsPrincipal use the Windows local or network group (either an NT domain or an Active Directory). So it only seems natural to map this functionality to Unix's group when running on *x operating systems.

The following sample use the WindowsPrincipal class in a simple interactive console application that let you query users and groups.

using System; using System.Runtime.InteropServices; using System.Security.Principal; class WinPrincipal { [DllImport ("advapi32.dll", CallingConvention=CallingConvention.StdCall)] public static extern bool LogonUser (string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); static void Principal (WindowsIdentity user) { WindowsPrincipal wp = new WindowsPrincipal (user); while (true) { Console.Write ("Enter group name: "); string group = Console.ReadLine (); if (group == String.Empty) break; Console.WriteLine ("\t{0} is {1}a member of {2}{3}", user.Name, (wp.IsInRole (group) ? String.Empty : "NOT "), group, Environment.NewLine); } } [STAThread] static void Main (string[] args) { bool posix = ((int) Environment.OSVersion.Platform == 128); while (true) { Console.Write ("Enter username:\t"); string username = Console.ReadLine (); if (username == String.Empty) break; WindowsIdentity user = null; // check for Posix versus Windows if (posix) { user = new WindowsIdentity (username); } else { Console.Write ("Enter domain:\t"); string domain = Console.ReadLine (); Console.Write ("Enter password:\t"); string password = Console.ReadLine (); IntPtr token = IntPtr.Zero; // note: we should close that handle LogonUser (username, domain, password, 2, 0, ref token); if (token == IntPtr.Zero) { Console.WriteLine ("Invalid username/domain/password"); continue; } user = new WindowsIdentity (token); } Principal (user); } } }

As we can see the only difference between the Windows and Linux version is done at runtime. The Windows version requires the domain and password to obtain a token (the only part requiring P/Invoke in the sample). Note that no impersonation is done in the sample (neither on Linux nor on Windows - as we don't change the process identity) so it doesn't require root access to be run.

WARNING: Again this is a sneak preview of uncommitted stuff, i.e. this isn't part of the latest Mono release (0.31).

Running on Windows

This is what the sample looks when run on Windows.

Enter username: test Enter domain: farscape Enter password: ******** Enter group name: BUILTIN\Administrators FARSCAPE\test is NOT a member of BUILTIN\Administrators Enter group name: BUILTIN\Users FARSCAPE\test is a member of BUILTIN\Users Enter group name: Enter username:

Running on Linux

This is what the sample looks when run on Linux.

Enter username: root Enter group name: root root is a member of root Enter group name: bin root is a member of bin Enter group name: adm root is a member of adm Enter group name: sys root is a member of sys Enter group name: nobody root is NOT a member of nobody Enter group name: Enter username: nobody Enter group name: nobody nobody is a member of nobody Enter group name: root nobody is NOT a member of root Enter group name: Enter username:

Now I have to merge the Linux and Windows versions of WindowsIdentity, WindowsImpersonationContext and WindowsPrincipal into a single version, test again and commit :-).


4/7/2004 22:39:07 | Comments | Permalink

Identity sneak preview

My security bible for .NET has always been .NET Framework Security. It's not a new book but quite frankly this is the book I still respect the most on .NET Security. However there is a big omission from the book, as it almost doesn't cover IIdentity and IPrincipal interfaces and related classes. While the basic for Identity/Principal are simple some aspects are more complex (e.g. WindowsIdentity, WindowsImpersonationContext ...) and can even be misleading. Thanksfully there is now an alternative and anyone interested in Identity and Principal should have a good look at the upcoming book by Keith Brown, A .NET Developer's Guide to Windows Security. A draft is available to read online.

WindowsIdentity implements the IIdentity interface (the simple part) but also adds some cool stuff like the ability to impersonate another user. This is really cool but, unless you have Windows 2003, it requires you to P/Invoke to get a user token - see Windows sample on MSDN.

Windows and UNIX have many differences in handling identities. Thanksfully the framework has a good abstraction for identities and principals, which should hide most differences for day to day usages. So the big questions becomes how do we get the cool stuff to work under Linux ?

Actually we can get a very nice mapping with WindowsIdentity even if there are some fundamental differences between the operating systems (so don't expect perfect portability here). Here's an example of what we can do...

using System; using System.IO; using System.Security.Principal; class WinId { static string file = "root.txt"; static void Show (string msg) { Console.WriteLine (msg); WindowsIdentity wi = WindowsIdentity.GetCurrent (); Console.WriteLine ("\tUserName:\t{0}", wi.Name); Console.WriteLine ("\tToken:\t\t{0}", wi.Token); Console.WriteLine (); } static void Show (Exception e) { Console.WriteLine ("Oops something has gone bad..."); Console.WriteLine (e.ToString ()); } static void DisplayFile (string filename) { Console.WriteLine ("Trying to read file {0}", filename); try { using (StreamReader sr = File.OpenText (filename)) { Console.WriteLine (sr.ReadToEnd ()); sr.Close (); } } catch (Exception e) { Show (e); } } static void DeleteFile (string filename) { Console.WriteLine ("Trying to delete file {0}", filename); try { File.Delete (filename); } catch (Exception e) { Show (e); } } [STAThread] static void Main (string[] args) { bool fileCreated = false; Show ("Current User"); if ((WindowsIdentity.GetCurrent ().Token == IntPtr.Zero) && ((int)Environment.OSVersion.Platform == 128)) { Console.WriteLine ("Cool you're root!"); Console.WriteLine ("Let's create a file {0}.", file); using (StreamWriter sw = new StreamWriter (file)) { sw.WriteLine ("FOR ROOT EYES ONLY"); sw.Close (); } fileCreated = true; DisplayFile (file); Console.WriteLine ("Now, who do you want to be today ? "); string username = Console.ReadLine (); WindowsIdentity nonroot = new WindowsIdentity (username); Console.WriteLine ("Trying to impersonate {0} (token: {1})", nonroot.Name, nonroot.Token); WindowsImpersonationContext wic = null; try { wic = nonroot.Impersonate (); Show ("Impersonated User"); DisplayFile (file); DeleteFile (file); } catch (Exception e) { Show (e); } finally { if (wic != null) { wic.Undo (); wic = null; } } Show ("Back to original User"); if (File.Exists (file)) { DisplayFile (file); DeleteFile (file); } else if (fileCreated) { Console.WriteLine ("Hey dude, where's my file ?"); } } else { Console.WriteLine ("Sorry, not much fun to have with non-root users."); } } }

First the sample will show you the current OS identity running the assembly. I say OS identity because this may be different than Thread.CurrentPrincipal.Identity something important to keep in mind when designing .NET applications dealing with identities.

The second part checks if you're running on a Unix system and, if so, if you are the super-user (root, which always has uid 0). The root user is important for this sample because it has the privilege to impersonate any user on the system. As the root we create a file (root.txt) and display it's content.

We now try to impersonate another user on the system. Note that root doesn't need the user password to impersonate the user - so, unlike Windows, no P/Invoke will be required here. As the new user we will try to display the file content, then try to delete the file.

Finally we will revert to the original process identity and, if the root.txt file still exists, display the file before deleting it.

Executing the sample

WARNING: Don't try this (yet) at home - that's what a sneak preview is for. While mcs, or even csc, can compile the sample code, neither Mono nor the MS runtime can execute it correctly right now (well MS will probably never run this under Linux). I'll update this entry when my patches are committed into CVS.

[root@rh9 home]# mono poupou/winid.exe Current User UserName: root Token: 0 Cool you're root! Let's create a file root.txt. Trying to read file root.txt FOR ROOT EYES ONLY Now, who do you want to be today ? nobody Trying to impersonate nobody (token: 99) Impersonated User UserName: nobody Token: 99 Trying to read file root.txt FOR ROOT EYES ONLY Trying to delete file root.txt Oops something has gone bad... System.UnauthorizedAccessException: Access to the path "root.txt" is denied. in <0x001df> System.IO.File:Delete (string) in <0x00024> WinId:DeleteFile (string) Back to original User UserName: root Token: 0 Trying to read file root.txt FOR ROOT EYES ONLY Trying to delete file root.txt

Ok, maybe the sample didn't work as you expected, like:

How could the user read the root file ?
Because by default the files are created as readable for everyone - umask
How could the user delete the root file ?
Just checking the file attributes isn't enough. If the user has write privileges in the directory he can delete the file (e.g. if the user is the directory's owner). Just be sure to run the sample if a directory where the impersonated user can't write to it (i.e. like /home/).
I got exceptions running the code ?
Some are normal. Remember that this will only works on POSIX compliant systems.

Ok, while this sample isn't portable (i.e. it won't work on Windows) it does have a big advantage over Windows - we didn't have to P/Invoke our way into any OS calls. Which means that the WindowsIdentity class work even better on Linux than on Windows :-).


4/5/2004 11:33:18 | Comments | Permalink

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.