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

Low-level Security Classes

Rate me:
Please Sign up or sign in to vote.
4.73/5 (13 votes)
1 Mar 2000 146.8K   2.4K   31   25
A set of classes to encapsulate the Win32 Security APIs
  • Download demo project - 17 Kb
  • Download source files - 4 Kb
  • If you have ever tried to do something simple like set a security descriptor for a Registry key you know what a pain the Win32 Security APIs can be. The process of initializing the various structures, allocating and deallocating memory and testing for error conditions is tedious and you end up with at least a page and a half of code which is difficult to read and doesn't lend itself to being reused next time you want to do the same thing. There had to be a better way, I thought. So I wrote a set of classes which take care of much of the donkey-work for you.

    All of the classes correspond to the structures used by the low-level Security APIs - ACEs, ACLs, SIDs, TRUSTEEs, Security Descriptors. Their member functions are in most cases the same as the calls you would use when working with the API. The crucial differences are:

    1. Often, a single constructor call is all that is needed to allocate and initialize an object
    2. The classes which need to allocate memory will release it safely when the object goes out of scope.

    Other than that, they are just thin wrappers. You can still get at the underlying objects if you need to, and you can also pass them to any of the API functions expecting a security object.

    The best documentation I can give you is some sample code, so here is an example of how to create a secure Registry key which can be read by any user but only changed by administrators. To compare this with code which does the same thing using only API calls, see Setting a Security Descriptor for a new object in the MSDN library. Notice how much longer it is.

    // Create a Registry key, granting KEY_READ access to everyone and 
    // full control to the Administrators group.
    
    // Initialize 2 EXPLICIT_ACCESS structures: 1 for Everyone,
    // 1 for Administrators. 
    CSid sidEveryone(CSid::WST_EVERYONE);
    CSid sidAdmins(CSid::WST_LOCALADMINS);
    
    CTrustee trEveryone(TRUSTEE_IS_WELL_KNOWN_GROUP, sidEveryone);
    CTrustee trAdmins(TRUSTEE_IS_GROUP, sidAdmins);
    
    EXPLICIT_ACCESS ea[2];
    
    ea[0] = CExplicitAccess(KEY_READ, SET_ACCESS, NO_INHERITANCE, trEveryone);
    ea[1] = CExplicitAccess(KEY_ALL_ACCESS, SET_ACCESS, NO_INHERITANCE, trAdmins);		

    Note the constructor for CSid which allows you to create a SID for any of the well-known groups such as Administrators, ordinary users, etc.

    // Create a new ACL and set the EA entries in it
    CAcl acl;
    if(acl.SetEntriesInAcl(2, ea) == ERROR_SUCCESS)
    {
    
    	// Initialize a security descriptor and add our ACL to it  
    	CSecurityDescriptor sd;
    	if(sd.SetSecurityDescriptorDacl(
    		TRUE,     // fDaclPresent flag   
    		acl, 
    		FALSE))   // not a default DACL 
    	{
    
    		// Initialize a security attributes structure.
    		CSecurityAttributes sa(sd, FALSE);
    
    		// Use the security attributes to set the security descriptor 
    		// when you create a key.
    		DWORD	dwDisposition;
    		HKEY	hKey;
     
    		RegCreateKeyEx(HKEY_CURRENT_USER, "mykey1", 0, "", 0, 
    			KEY_READ | KEY_WRITE, sa, &hKey, &dwDisposition); 
    
    	}
    
    }

    That's it! If you have any suggestions or problems to report, post them here. There are plenty of ways this idea could be extended if you are of an inclination to do so.

    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
    New Zealand New Zealand
    This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

    Comments and Discussions

     
    QuestionHow can I create a new windows user? Pin
    pengok7-Dec-04 14:17
    pengok7-Dec-04 14:17 
    Generaldoes not work :-( Pin
    emmi18-Jul-04 20:44
    emmi18-Jul-04 20:44 
    GeneralRe: Please see response of the first message on this board! Pin
    Behzad Ebrahimi24-Jul-04 0:00
    Behzad Ebrahimi24-Jul-04 0:00 
    GeneralWrite access right Pin
    Jianping Yan15-May-04 12:36
    Jianping Yan15-May-04 12:36 
    GeneralWorks fine on NT 4.0, too. Pin
    Anonymous1-Aug-03 10:12
    Anonymous1-Aug-03 10:12 
    Generalone Question Pin
    osirix15-Jul-03 6:23
    osirix15-Jul-03 6:23 
    Generalchange permissions of an existing key Pin
    mpester6-Mar-03 2:07
    mpester6-Mar-03 2:07 
    GeneralRe: change permissions of an existing key Pin
    lesashwad25-Jan-06 10:36
    lesashwad25-Jan-06 10:36 
    GeneralRe: change permissions of an existing key Pin
    Atlence31-Jan-06 2:59
    Atlence31-Jan-06 2:59 
    Generalregistry security templates Pin
    travisowenjones22-Jan-03 9:57
    susstravisowenjones22-Jan-03 9:57 
    GeneralSlight syntax fix Pin
    John P. Curtis16-Dec-02 17:07
    John P. Curtis16-Dec-02 17:07 
    Generaluserlist Pin
    Haresh15-May-02 1:36
    Haresh15-May-02 1:36 
    GeneralRe: userlist Pin
    16-May-02 12:43
    suss16-May-02 12:43 
    GeneralRe: userlist Pin
    Haresh16-May-02 19:13
    Haresh16-May-02 19:13 
    GeneralIntercepting user lockout Pin
    21-Nov-01 6:20
    suss21-Nov-01 6:20 
    GeneralChanging a directory's security options in Win2000 Pin
    11-Jul-01 1:50
    suss11-Jul-01 1:50 
    GeneralChanging a directory's security options in Win2000 Pin
    11-Jul-01 1:50
    suss11-Jul-01 1:50 
    QuestionHow to validate a username and password? Pin
    Alvaro Mendez12-Jan-01 8:51
    Alvaro Mendez12-Jan-01 8:51 
    AnswerRe: How to validate a username and password? Pin
    Todd Jeffreys12-Jan-01 11:02
    Todd Jeffreys12-Jan-01 11:02 
    Here's a member function for a class that i made. It validates a user!

    // Performs users authentication, sets the user token if successful
    bool CWin32WorkUnit::VerifyUser(char *user,char *password)
    {
    if (m_hUserToken)
    CloseHandle(m_hUserToken);
    if (LogonUser(user,".",password,LOGON32_LOGON_BATCH,LOGON32_PROVIDER_DEFAULT,&m_hUserToken))
    return true;
    return false;
    }

    The m_hUserToken is a HANDLE member

    You will have to have the required privileges to do this. Here's what I did.

    // This function will attempt to add a privilege to the current user
    bool CWin32SocketServer::AddPrivilege(LPTSTR pStrPrivilege)
    {
    LSA_OBJECT_ATTRIBUTES ObjectAttributes;
    WCHAR wszString[128];
    DWORD wszStringLength;
    LSA_UNICODE_STRING lsaszServer;
    NTSTATUS ntsResult;
    LSA_HANDLE lsahPolicyHandle;
    PSID pSid;
    SID_NAME_USE nameUse;
    TCHAR userName[256],domain[256];
    DWORD len=sizeof(userName);
    DWORD lenD=sizeof(domain);
    bool bRet=false;
    // first determine which user we are
    GetUserName(userName,&len);
    // Object attributes are reserved, so initalize to zeroes.
    ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));

    //Initialize an LSA_UNICODE_STRING structure to the server name.
    // The server machine is just . for the local machine
    wcscpy(wszString,L".");
    wszStringLength = wcslen(wszString);
    lsaszServer.Buffer = wszString;
    lsaszServer.Length = (USHORT) wszStringLength * sizeof(WCHAR);
    lsaszServer.MaximumLength=(USHORT)(wszStringLength+1) * sizeof(WCHAR);

    // Attempt to open the policy.
    ntsResult = LsaOpenPolicy(&lsaszServer,&ObjectAttributes,POLICY_CREATE_ACCOUNT | POLICY_LOOKUP_NAMES,&lsahPolicyHandle);

    // lookup the SID of the current user, will return size of structure
    LookupAccountName(NULL,userName,NULL,&len,domain,&lenD,&nameUse);
    // allocate it
    pSid=new BYTE[len];
    if (pSid)
    {
    // lookup again, get the SID
    lenD=sizeof(domain);
    LookupAccountName(NULL,userName,pSid,&len,domain,&lenD,&nameUse);

    //Initialize an LSA_UNICODE_STRING structure to the token to add
    MultiByteToWideChar(CP_ACP,0,pStrPrivilege,-1,wszString,sizeof(wszString));
    wszStringLength = wcslen(wszString);
    lsaszServer.Buffer = wszString;
    lsaszServer.Length = (USHORT) wszStringLength * sizeof(WCHAR);
    lsaszServer.MaximumLength=(USHORT)(wszStringLength+1) * sizeof(WCHAR);

    // attempt to add it
    ntsResult=LsaAddAccountRights(lsahPolicyHandle,pSid,&lsaszServer,1);
    if (ntsResult == 0) // success
    bRet=true;

    delete [] pSid;
    }
    //Freeing the policy object handle
    LsaClose(lsahPolicyHandle);
    return bRet;
    }

    // This function enables the privilege for the user (if the user has the privilege
    // to start!) If the user doesn't have the privilege, it attempts to add it
    bool CWin32SocketServer::EnablePrivilege(LPTSTR pStrPrivilege)
    {
    char error[1024];
    HANDLE hProcessToken;
    TOKEN_PRIVILEGES privilege;
    bool bRes=false;

    // open our process token
    if (OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&hProcessToken))
    {
    // initialize the structure of our token
    privilege.PrivilegeCount=1;
    privilege.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_USED_FOR_ACCESS;
    LookupPrivilegeValue(NULL,pStrPrivilege,&privilege.Privileges[0].Luid);

    // do the adjustment, enable it
    AdjustTokenPrivileges(hProcessToken,FALSE,&privilege,0,0,0);
    // check the result
    if (GetLastError() != ERROR_SUCCESS)
    {
    // didn't work! try to add it to the current account
    if (!AddPrivilege(pStrPrivilege))
    {
    // if we can't add it and can't enable it, we're screwed
    strcpy(error,"I was unable to add privilege ");
    strcat(error,pStrPrivilege);
    strcat(error,".\r\nThis program will not be able to be run ever");
    MessageBox(NULL,error,NULL,MB_OK);
    // get outta here!
    ExitProcess(-1);
    }
    bRes=false;
    }
    else
    {
    bRes=true;
    }
    // close the process token
    CloseHandle(hProcessToken);
    }
    return bRes;
    }


    Then when i run the app, in say InitApp(), i execute this code

    bool bReboot=false;

    // enable privleges so people can be validated
    if (!EnablePrivilege(SE_TCB_NAME))
    bReboot=true;
    if (!EnablePrivilege(SE_CHANGE_NOTIFY_NAME))
    bReboot=true;
    if (!EnablePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME))
    bReboot=true;
    if (!EnablePrivilege(SE_INCREASE_QUOTA_NAME))
    bReboot=true;

    if (bReboot)
    {
    // i must reboot to change
    MessageBox(NULL,"You must logoff for privilege changes to take effect\r\nPress OK to logoff now",NULL,MB_OK);
    // do the reboot now
    ExitWindowsEx(EWX_LOGOFF,0);
    return false;
    }
    GeneralRe: How to validate a username and password? Pin
    Alvaro Mendez12-Jan-01 12:09
    Alvaro Mendez12-Jan-01 12:09 
    GeneralRe: How to validate a username and password? Pin
    27-Apr-01 4:30
    suss27-Apr-01 4:30 
    GeneralRe: How to validate a username and password? Pin
    12-Nov-01 2:18
    suss12-Nov-01 2:18 
    GeneralInvalid SID error when creating ACL Pin
    Jeff Mcaffee28-Sep-00 11:14
    sussJeff Mcaffee28-Sep-00 11:14 
    GeneralRe: Invalid SID error when creating ACL Pin
    Peter Kenyon28-Sep-00 12:09
    Peter Kenyon28-Sep-00 12:09 
    GeneralRe: Invalid SID error when creating ACL Pin
    28-Dec-00 12:45
    suss28-Dec-00 12:45 

    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.