Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C++
Article

Securing a Registry Key ACL using .NET

Rate me:
Please Sign up or sign in to vote.
4.09/5 (7 votes)
19 May 20054 min read 67.5K   20   5
Managing ACLs and securing registry keys 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_ACCESS structs 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_DESCRIPTOR struct 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_ATTRIBUTES struct. 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.

Managed C++ source file

C++
// This is the main DLL file.

#include "stdafx.h"

#include "SecureRegistry.h"

namespace SecureRegistry
{
    long KeyManager::CreateRestrictedRegKey(String* account)
    {
        // Convert .NET string type to LPCTSTR (remember to free LPCTSTR after use)
        LPCTSTR accountName = (const char *)
               (Marshal::StringToHGlobalAnsi(account)).ToPointer();

        DWORD dwRes, dwDisposition;
        PSID pAdminSID = NULL;
        PSID pUserSID = NULL;
        PACL pACL = NULL;
        PSECURITY_DESCRIPTOR pSD = NULL;
        EXPLICIT_ACCESS ea[2];
        SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
        SECURITY_ATTRIBUTES sa;
        DWORD cbSid = 0;
        DWORD dwRefDomain = NULL;
        SID_NAME_USE peUse;
        HKEY softwareKey = NULL;
        HKEY companyKey = NULL;
        HKEY securedKey = NULL;
        LONG lRes = 0;

        // Lookup SID for specific user account that is passed in to this method
        if(!LookupAccountName(NULL,accountName,NULL, 
            &cbSid,NULL,&dwRefDomain,&peUse))
        {
            pUserSID = LocalAlloc(LPTR,cbSid);
            TCHAR refDomain[128];
            if(!LookupAccountName(NULL,accountName,pUserSID, 
               &cbSid,refDomain,&dwRefDomain,&peUse))
            {
                System::Console::WriteLine("Unable to initialize" 
                       " User SID - {0}", GetLastError().ToString());
                Marshal::FreeHGlobal(System::IntPtr((void*)accountName));
                return 0;
            }
        }

        // Frees LPCTSTR
        Marshal::FreeHGlobal(System::IntPtr((void*)accountName));

        // Validate SID before continuing
        if(!IsValidSid(pUserSID))
        {
            System::Console::WriteLine("Invalid UserSID");
            goto Cleanup;
        }

        // Lookup a well known group, Administrators
        if(!AllocateAndInitializeSid(&SIDAuthNT, 2, 
            SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 
            0,0,0,0,0,0,&pAdminSID))
        {
            System::Console::WriteLine("Unable to initialize" 
                    " Admin SID - {0}", GetLastError().ToString());
            goto Cleanup;
        }

        // Create two EXPLICIT_ATTRIBUTES to be used in the ACL
        // Administrators will have full access in this ACL
        ea[0].grfAccessPermissions = KEY_ALL_ACCESS;
        ea[0].grfAccessMode = SET_ACCESS;
        ea[0].grfInheritance = NO_INHERITANCE;
        ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
        ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
        ea[0].Trustee.ptstrName = (LPTSTR) pAdminSID;

        // The specified user will have read access in the ACL
        ea[1].grfAccessPermissions = KEY_READ;
        ea[1].grfAccessMode = SET_ACCESS;
        ea[1].grfInheritance = NO_INHERITANCE;
        ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
        ea[1].Trustee.TrusteeType = TRUSTEE_IS_USER;
        ea[1].Trustee.ptstrName = (LPTSTR) pUserSID;

        // Create the new ACL containing the EXPLICIT_ATTRIBUTES
        dwRes = SetEntriesInAcl(2, ea, NULL, &pACL);
        if(dwRes != ERROR_SUCCESS)
        {
            System::Console::WriteLine("Unable to set entries" 
                    " in ACL - {0}", GetLastError().ToString());
            goto Cleanup;
        }

        // Allocate memory for a SECURITY_DESCRIPTOR
        pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, 
                           SECURITY_DESCRIPTOR_MIN_LENGTH);

        // Initialize a new SECURITY_DESCRIPTOR
        if(!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
        {
            System::Console::WriteLine("Unable to initialize" 
                 " security descriptor - {0}", GetLastError().ToString());
            goto Cleanup;
        }

        // Set the ACL to the SECURITY_DESCRIPTOR
        if(!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE))
        {
            System::Console::WriteLine("Unable to set" 
               " security descriptor DACL - {0}", GetLastError().ToString());
            goto Cleanup;
        }

        // Apply the SECURITY_DESCRIPTOR to the SECURITY_ATTRIBUTES struct
        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.lpSecurityDescriptor = pSD;
        sa.bInheritHandle = FALSE;

        // Lookup registry key where security should be applied
        lRes = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
                 "SOFTWARE",0,KEY_ALL_ACCESS,&softwareKey);
        if(lRes == ERROR_SUCCESS)
        {
            lRes = RegOpenKeyEx(softwareKey,"MyCompany", 0, 
                                     KEY_ALL_ACCESS,&companyKey);
        }
        if(lRes == ERROR_SUCCESS)
        {
            // Create the new registry key, applying the SECURITY_ATTRIBUTES
            lRes = RegCreateKeyEx(companyKey,"MySecuredKey",0,"",0,
                KEY_READ | KEY_WRITE, &sa, &securedKey, &dwDisposition);
        }

Cleanup:  // Free any resources allocated during execution of this method
        {
            if(pAdminSID)
                FreeSid(pAdminSID);
            if(pUserSID)
                FreeSid(pUserSID);
            if(pACL)
                LocalFree(pACL);
            if(pSD)
                LocalFree(pSD);
            if(securedKey)
                LocalFree(securedKey);
            if(companyKey)
                LocalFree(companyKey);
            if(softwareKey)
                LocalFree(softwareKey);
        }

        return lRes;
    }
}

Header file

C++
// SecureRegKeyNET.h

#pragma once

using namespace System;
using namespace System::Runtime::InteropServices;

namespace SecureRegistry
{
    public __gc class KeyManager
    {
    public:
        static long CreateRestrictedRegKey(String* account);
    };
}

stadfx.h header file

C++
#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.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Systems Analyst specializing in Java and C# development.

Comments and Discussions

 
GeneralRegCreateKeyEx Pin
Member 68943213-Jun-05 3:34
Member 68943213-Jun-05 3:34 
Hi,

This is Lalitha.
I have used the code given in C++ and made use of it in my C# code to create a new Registry key and grant access to that key.

I believe that "RegCreateKeyEx" creates or open the key if it is existing.

If I want to grant KEY_ALL_ACCESS to the already existing Registry Key, I am NOT able to this.

Please suggest.

Thanks in advance.

Regards,
Lalitha

Lalitha, TCS India
QuestionHard isn't it? Pin
oshah22-May-05 10:35
oshah22-May-05 10:35 
AnswerRe: Hard isn't it? Pin
Dave Curylo, MCAD23-May-05 5:13
Dave Curylo, MCAD23-May-05 5:13 
GeneralRe: Hard isn't it? Pin
oshah24-May-05 11:50
oshah24-May-05 11:50 
GeneralRe: Hard isn't it? Pin
Ian_Rintoul4-Aug-09 8:54
Ian_Rintoul4-Aug-09 8:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.