Developer Product Briefs

Lock Down Your Files

Use the .NET Framework 2.0 to perform file access control, which serves as your innermost wall to protect the network castle from intruders.

Lock Down Your Files
A single, large barrier is not as good as multiple layers of security when you want to build secure apps. Improve your security by taking advantage of file access control with the .NET Framework 2.0's System.Security.AccessControl namespace.
by Jani Järvinen

January 27, 2006

Technology Toolbox: C#, .NET 2.0, Windows 2000, XP, or Server 2003

Security is a ubiquitous part of any computer system of any size. A single large barrier is less effective than implementing multiple layers of security when it comes to creating secure applications. Fortunately, building such security layers isn't difficult; adding security at different application levels simply requires that you approach the problem with some foresight. I'll explain how to approach solving that problem using the .NET Framework 2.0 and its new security-related namespace, System.Security.AccessControl.

This namespace gives you low-level access to many Windows functions, enabling you to manipulate Windows Access Control Lists (ACLs) and associate them with different object types in the operating system. For example, you can now associate files, registry keys, and mutexes with an access control list. This is important when you want to build applications that implement robust levels of security.

Contrast this to the .NET Framework 1.0 and 1.1, where security-related classes in the Framework Class Library (FCL) were related primarily to Code Access Security (CAS), cryptography, or security in ASP.NET Web applications. These features are useful, but they represent only a small part of all the security features Windows makes available. For example, you might have missed the lower-level security classes to manipulate kernel object permissions, file access control lists, and so on. The initial implementations of the .NET Framework required that you call the Windows API functions directly using Platform Invoke (P/Invoke) .

Before diving into the specifics of how to manipulate access-control list functions from your applications, it might be helpful to take a close look at the features that Windows includes natively, as well as how they work and what some of their limitations are. The .NET Framework relies on the core functionality of the Windows systems it runs on to implement access-control lists.

Windows operating systems build access control directly into the NT-based operating system family, which includes Windows NT, Windows 2000, Windows XP, and Window Server 2003. Unfortunately, the Windows 95/98/Me versions don't support access-control lists, which means that the .NET classes related to access control won't work, because they rely on the innate features of the operating system. You can't take advantage of what isn't there.

Access control lets you provide security for different types of objects known as securable objects. Such objects include (but are not limited to): files and directories on NTFS formatted drives; Registry keys; Windows services; printers; network shares; Win32 event objects, mutexes and semaphores; Active Directory objects; and processes and threads. Windows can associate a security descriptor (SD) with each of these object types (see Figure 1) .

Inside the Security Descriptor
A security descriptor is an important concept in Windows security. It contains the owner of the object, a discretionary access control list (DACL), and a system access-control list (SACL). It's easy to confuse these different terms when learning about access-control programming, so I'll provide you with a quick run-down of these key terms and summarize how they are pertinent to the using access-control lists from .NET. Note that you can always turn to the Win32 API SDK documentation if you have additional questions my own short descriptions don't cover.

The DACL contains one or more entries that control how you can access the object that contains the ACL. A SACL contains one or more entries that control event logging when the object is accessed. The DACL and SACL are examples of Access Control Lists (ACLs). ACLs can contain one or more items called Access Control (ACE).

The work that an ACE can control depends on the object type, but common types of actions include reading, writing, and deleting objects. An ACE can either allow or deny such work. DACLs fall into one of two categories: an access-denied ACE or an access-allowed ACE.

The final piece of the puzzle is the Security Identifier (SID). An ACE uses an SID to identify an account. SIDs can also describe the owner of an object. Internally, a SID is nothing more than a binary representation of a Windows user account or a group. It is also possible to represents SIDs as strings, which gives you an easy way to transfer SID data from system to another or store data for later use.

That's all the background you need to take advantage of the System.Security.AccessControl namespace from your .NET 2.0 applications. I want to keep things simple, so topics such as impersonation, ACE accumulation, inheritance, and empty/null DACLs are beyond the scope of this article. Again, refer to the Win32 API SDK documentation for more information if you run into topics not covered in this article specifically.

The System.Security.AccessControl namespace contains more than 70 classes and enumerations that can help you build solutions when programming against an access control. The .NET Framework 2.0 also extends many classes you might already use from earlier versions of .NET. For example, the System.IO.File class now includes methods such as GetAccessControl and SetAccessControl to get and set access control entries. The earlier .NET versions also included the System.Security.Principal namespace, which you need when manipulating SIDs.

.NET is fully object oriented, so it makes sense to have a class to correspond to each of the different players in the access control game. However, doing this is not a simple task because each of the securable object types (files, registry keys, and so on) require different methods and properties. This leads to a somewhat complex design. The AccessControl namespace attempts to minimize this complexity by making heavy use of inheritance. You should pay particular attention to the inheritance hierarchies when you study the classes in the AccessControl namespace.

Build an Access Control App
You're now ready to begin building an app using the System.Security.AccessControl namespace (see Figure 2). The sample app doesn't demonstrate every class available in the namespace, but does illustrate how to use the most significant ones.

When you build security apps, it's important to know who is running the application. You can determine the user name by using the static GetCurrent method of the System.Security.Principal.WindowsIdentity class:

System.Security.Principal. _
   WindowsIdentity self = _
   System.Security.Principal. _
   WindowsIdentity.GetCurrent();
System.Security.Principal. _
   SecurityIdentifier selfSID =
   self.User;

If you have an instance of the WindowsIdentity class, you can set its User property to return an instance of the SecurityIdentifier class, also part of the System.Security.Principal namespace. Both the SecurityIdentifier class and the User property of the WindowsIdentity class are new features in .NET 2.0.

The SecurityIdentifier class is an SID. It inherits from the abstract IdentityReference class (also in the System.Security.Principal namespace). The SecurityIdentifier class includes a convenient Value property that returns the string representation of the associated identifier. You can see such a string representation under the current user name in the example application.

Note that file-based security is available only on NTFS formatted drives. This means that you can't associate security settings with your files on Windows XP if you use a FAT32 partition. In code, it is easy to use the System.IO.DriveInfo class and its DriveFormat property to check if a drive is formatted as NTFS or not.

DriveInfo[] infos = _
   DriveInfo.GetDrives();
foreach (DriveInfo info in infos) {
   if ((info.Name == "C:\\") && _    (info.DriveFormat == "NTFS"))    {
     // drive is NTFS!
   } }

The next step is to enumerate the ACE entries of a file (see Listing 1). Clicking on the Show Rights button in the sample application calls the custom ShowFileRights method. This method calls the static GetAccessControl method of the System.IO.File class, returning an object of type System.Security.AccessControl.FileSecurity, which is both the discretionary ACL and the system (audit) ACL of the selected file.

The FileSecurity class contains a method named GetAccessRules that lets you read the discretionary ACL from the file. You build the sample application around DACLs instead of SACLs, so I won't explain how to read or modify the system ACLs. However, you can accomplish this using the FileSecurity class' GetAuditRules and SetAuditRules methods.

The GetAccessRules method returns an object of type AuthorizationRuleCollection. This class holds one or more AuthorizationRule objects. The AuthorizationRule class is an abstract class and the base class for both AccessRule and AuditRule. When you call the GetAccessRules of a FileSecurity object, you get an AuthorizationRuleCollection containing FileSystemAccessRule objects.

Listing 1 enumerates the entries of the AuthorizationRuleCollection using a simple foreach loop, then creates a new form to show the rules on the screen (see Figure 3). This dialog is similar to the one Windows XP itself shows.

Edit ACLs
So far, you know how to read ACLs, but you also need to be able to edit them. For example, assume you want to change the permissions so that unauthorized people can't read a confidential file. You can do this by using the sample application's "Deny Read From Self" button (see Listing 2). You begin by using the WindowsIdentity.GetCurrent method to acquire the Security Identifier of the user running the application, then take advantage of the User property to get an instance of the SecurityIdentifier class.

At this point, the SecurityIdentifier represents the current user. Next, you create an instance of the FileSystemAccessRule class, the same instance you use when enumerating the DACL entries. The constructor accepts three parameters: the SID of the user or group to which you wish to assign permissions, the permissions you want to assign (using an enumeration of type FileSystemRights), and the ACE type (allow or deny).

Next, read the existing DACL from the file using the System.IO.File.GetAccessControl method, add the newly created FileSystemAccessRule to the returned collection, and then associate the new DACL with the file. Do this with the static SetAccessControl method that is the counterpart of the GetAccessControl method.

You can now read and write relevant permissions using the System.Security.AccessControl namespace. The next step is to use some of the new classes in this namespace to manipulate access control entries efficiently. For example, you can use the new classes to accomplish routine tasks such as converting user account names to SIDs, creating security descriptors (SDs) from SDDL language strings, and so on. You can see how to take advantage of several of these abilities by clicking on the sample application's group box, "Low-level APIs."

For example, you can take advantage of a combo box that lists all the well-known SID values in the system and lets you convert them to a SID string. A well-known SID identifies a generic user group or a generic user. Examples include the Administrator account, the Everyone group ("World" in SDK parlance), and Guests. In .NET, you can use the WellKnownSidType enumeration to access these values. The sample app populates the combo box with this code on start-up:

System.Array sids = System.Enum. _
   GetValues(typeof( _
   WellKnownSidType));
foreach (WellKnownSidType _    sid in sids) {    wellKnownSIDsComboBox. _      Items.Add(sid.ToString()); }

Clicking on the Show SID value button prompts the app to show the given well-known SID value on the screen as a string. Some well-known SIDs have a static presentation, which means they are the same from computer to computer (or domain to domain). For example, the WorldSid equals "S-1-1-0" everywhere. But some SIDs have a varying part in them that is associated with the current domain, or the non-existence of one, the computer. Thus, you cannot calculate these SID values without knowing something called the domain SID. The good news is that this domain SID is easy to extract because the SecurityIdentifier class has a property named AccountDomainSid that returns the domain SID from the given SID.

This article has provided a cursory introduction to the new System.Security.AccessControl namespace in the .NET Framework Class Library. Programming with the new classes is straightforward—once you have mastered the terminology and the class hierarchies. But once you do, you should be able to get going in no time at all.

A fun topic to explore next might include ACL manipulation when it comes to registry keys. The sample code that manipulates the files in this articles source code should prove helpful for solving that task as well. I recommend you study it carefully.

About the Author
Jani Järvinen works as a technical manager for software development at Moonsoft Oy in Finland. He is a C# Most Valuable Professional (MVP). E-mail him at janij@dystopia.fi.

comments powered by Disqus
Most   Popular
Upcoming Events

.NET Insight

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.