Securing a Registry Key ACL using .NET

Introduction

The .NET API provided by Microsoft allows you to create, modify, and delete keys in the Windows registry easily, all from managed code. The problem comes in when you want to control access to the registry keys, which, until the release of .NET 2.0, must be performed by Win32 API calls. For example, you may store secure information for your application in the registry and you want to be sure that information is only available to the users of that application and not read or modified by other users. To do so, your application must modify the Access Control List (ACL) for that registry key.

This article demonstrates the process of looking up a Security Identifier (SID) for either a user account or a built-in group (like Administrators), creating Access Control Entities (ACE) to define the access rights for each SID, and adding those ACEs to an ACL which is then used to create a registry key with restricted access.

Background

My first shot at this task attempted to perform the entire process in C#, using P/Invoke to make calls to the Win32 APIs. I gave up and moved to Managed C++ after unsuccessfully troubleshooting a NullPointerException in the call to SetEntriesInAcl(). The majority of this code will work in unmanaged C++ or C just as well; the only .NET specific code deals with marshalling the string parameter from .NET to an LPCTSTR for the Win32 calls and a few lines of console output.

Using the code

This code is intended to demonstrate a number of functions, including looking up a SID based on username or built in group, creating an ACL, and applying that ACL to a registry key. You may only want to lookup a SID, or you may want to apply your ACL to a file. The process is generally the same, but you will need to deviate from the code here to meet your application needs. To apply an ACL to a file rather than a registry key, instead of passing the ACL to a SECURITY_ATTRIBUTE that is passed to RegCreateKeyEx, you will pass the ACL to a SECURITY_DESCRIPTOR to pass to SetFileSecurity.

The code initially takes in a .NET string and creates an LPCTSTR which is necessary for the Win32 API calls. Next, a call to LookupAccountName is used to get the SID for that user. To retrieve the SID for a well known group (Administrators, in this case), a call to AllocateAndInitializeSid is used.

Next, the code creates an array of EXPLICIT_ACCESSstructs where the access is defined for each SID. These structures will equate to the ACEs in the final ACL. The order of the structs in the array is important, since an ACL is evaluated from top to bottom. That is, if a user is explicitly given read only access in the first structure, and a group to which that user belongs is given full control in a later struct, the first structure will take precedence and the user will have read only access.

Once the EXPLICIT_ACCESS array is created, a call is made to SetEntriesInAcl to actually create the ACL. This method also accepts a pointer to another ACL, if your intention is to modify a current ACL rather than creating a new one. After the ACL is created, it's just a few more API calls to InitializeSecurityDescriptor to create a SECURITY_DESCRIPTORstruct and SetSecurityDescriptorDacl to set the ACL to the SECURITY_DESCRIPTOR. This SECURITY_DESCRIPTOR is the one you would pass in a call to SetFileSecurity if you wanted to set the ACL on a file.

Once the SECURITY_DESCRIPTOR is ready, it can be set to a SECURITY_ATTRIBUTESstruct. This SECURITY_ATTRIBUTES structure is passed by reference to the RegCreateKeyEx method. Note that you will need to open any higher level registry keys such as the the SOFTWARE key to access the location where the new registry key will be created. Finally, there is a labeled section of code called Cleanup: that releases any resources that were allocated during the method's execution.

stadfx.h header file

#pragma once
#include<windows.h>#include<aclapi.h>

I look forward to the day when .NET 2.0 is released and I don't have to use Win32 API calls to manage ACLs on registry keys. Until then, I am happy to use C++ to perform the dirty work and provide a simple static method call to use in other .NET applications. If you have any questions, suggestions, or improvements to this code, or if you managed to successfully tackle this using P/Invoke, please let me know.

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.

Even in native C++, ACL editing can be tough. I came close to madness when I was setting ACLs using the NT3.x APIs (and this was in native C!). My finished program only worked for one registry ACL, was not reusable and ended up being unmaintainable. I don't really want to see that code again.

Before continuing, you should stop using SetEntriesInAcl to set your ACLs. SetEntriesInAcl was an experimental API for NT4 that has been obsoleted by ConvertStringSecurityDescriptorToSecurityDescriptor.

SetEntriesInAcl doesn't work in NT3.x, it doesn't work in Win9x, it doesn't work in NT4.0 (pre-SP3), and it has been replaced by SDDL in Win2000. SetEntriesInAcl just doesn't work--don't use it. Next, although it doesn't apply to your code, make sure you prefer SetNamedSecurityInfo over SetFileSecurity / RegSetKeySecurity. This is to preserve auto-inheritance settings when you edit a registry key.

.NET v1.1: Microsoft have written a class library at GotDotNet that is able to set DACLs for registry keys as well as files/directories/WMI/process objects. Make sure you read the comments for code fixes.

However, my ultimate recommendation is to wait for .NET v2.0 to come out. What I've said above will no longer apply once .NET v2.0 is released .

I appreciate the link to the .NET class library because that helps fill in the gap until .NET 2.0 is released. However, don't really see the advantage to using SDDL in this case. To say "SetEntriesInAcl just doesn't work" is simply a false statement because it plainly does work, on the same OS versions as the SDDL call. It's not an experimental method or part of some undocumented API, it's a usable, supported piece of the Win32 Platform SDK.

ConvertStringSecurityDescriptorToSecurityDescriptor is part of the same library as SetEntriesInAcl, so neither of them work in Win9x or NT 4.0. They both have the same system requirements and both require about the same steps to implement, so what is the advantage to using SDDL? The only difference is that to use SDDL, a developer needs to write code to build the SDDL string instead of writing code to build the EXPLICIT_ACCESS structures. There is always more than one way to "skin a cat".

Dave Curylo, MCAD wrote:
ConvertStringSecurityDescriptorToSecurityDescriptor is part of the same library as SetEntriesInAcl, so neither of them work in Win9x or NT 4.0.

Correct, but there is a difference between the two. A very important difference.

Using SDDL is easier than using SetEntriesInAcl, ConvertStringSecurityDescriptorToSecurityDescriptor only requires you to manipulate strings. Manipulating strings is far easier to do in .NET than manipulating structures, particularly a complex structure like EXPLICIT_ACCESS. Had you chosen to use SDDL, you may not have given up doing this in C#:

Given the choice of manipulating a string (plenty of built in string/text classes to help you) or creating a nonblittable array of structures that need to be marshalled as an LPArray, I'd choose the strings.

Not so big of an advantage in your case, but you can also set the owner, group, SACL, and inheritance settings all in one command. No need to call InitializeSecurityDescriptor, or SetSecurityDescriptorDacl, or BuildTrusteeWithName or even SetSecurityDescriptorControl.

There is another way to "skin this cat" (not including .NET 2). You can resort to the low level APIs (such as AddAce, AddAccessDeniedAce, AddAllowedObjectAce). However, if you use these APIs, you have to worry about conflicting ACE entries, ordering of ACEs and buffer size issues. You chose SetEntriesInAcl over the low level APIs because it was easier to program... why not make it that bit easier for yourself by using SDDL?