November 1999
The COM+ Security Model Gets You out of the Security Programming Business |
COM+ security insulates developers from the Windows and RPC security mechanisms so that as these technologies evolve, components will be able to derive benefits from the new security services automatically. This security model is built around the Security Support Provider Interface. |
This article assumes you're familiar with C++, COM, Security |
Guy Eddon (guyeddon@codemarine.com) is a senior developer at CodeMarine, a leading development and training institute specializing in COM+. This article is drawn from his book,
Inside COM+ Base Services, which will be released by Microsoft Press this fall, and his upcoming book, Inside COM+ Component Services, also to be published by Microsoft Press. |
Security is one area of COM+ that can bite you where you least expect it. COM+ offers component developers various declarative and programmatic security options. The distributed security model supported by Microsoft® Windows® 2000 focuses on two primary areas: authentication and access control. Authentication is usually encountered when a user attempts to log on to a network or call a process on another machine. In such cases, the system wants to be sure that the user is who she claims to be. Access control in Windows enables applications to selectively grant or deny certain users access to specific services. While any executable code presents a potential security risk, Windows lessens this risk by limiting the access permissions of a given process, depending on the user account it is running under. The COM+ Security Model
Authentication control is used to ensure that a network transmission is authentic and to protect the data from unauthorized viewers. Different authentication levels can be set so that all the data is encrypted before being transmitted. Identity control specifies the security credentials under which the component will execute. These credentials can be a specific user account configured for this purpose or the security credentials of the client application. Security information for COM+ components is configured in two ways: declarative security and programmatic security. Declarative security settings are configured in the COM+ catalog external to the component. a system administrator typically configures declarative security. Programmatic security is incorporated into a component by the developer. Activation, access, authentication, and identity security settings for a component can be configured in the declarative manner via the COM+ catalog, using the Component Services administrative tool or the Distributed COM Configuration utility (dcomcnfg.exe). The Distributed COM Configuration utility is now used to manage unconfigured components. Access and authentication security can also be controlled programmatically by using several interfaces and helper functions provided by COM+. Activation and identity security cannot be controlled programmatically because these settings must be specified before a component is launched. The COM+ Security Packages
Declarative Security
|
![]() |
Figure 1 Component Services Default Properties |
The Default Authentication Level setting specifies the base authentication level that will be used on this system, assuming that a component does not override the value programmatically or through other declarative settings. When the system is first installed, this setting is configured for connect-level authentication. The possible authentication levels and their attributes are shown in Figure 2. The Default Impersonation Level setting specifies the base impersonation level that clients running on this system will grant to their servers, again assuming that a component does not override this value. Impersonation levels are used to protect the client from rogue components. From the client's point of view, anonymous-level impersonation is the most secure because the component cannot obtain any information about the client. (Note that anonymous-level impersonation is not supported for remote calls.) With each successive impersonation level, a component is granted further liberties with the client's security credentials. When the system is first installed, this setting is configured for identify-level impersonation. The possible impersonation levels and their attributes are shown in Figure 3. Note that Windows NT supports only the RPC_C_ IMP_LEVEL_IDENTIFY and RPC_C_IMP_LEVEL_IMPERSONATE impersonation levels; Windows 2000 adds support for the RPC_C_IMP_LEVEL_DELEGATE impersonation level when using the Kerberos security protocol. The "Provide additional security for reference tracking" option indicates whether calls to the IUnknown::AddRef and IUnknown::Release methods are secured. When the system is first installed, this option is turned off. Checking this option causes COM+ to perform additional callbacks to authenticate distributed reference count calls, making sure that objects are not released maliciously. This option will improve the security of the system, but will slow execution speed. |
![]() |
Figure 4 The Default Security Tab |
The Component Services administration tool's Default Security tab, shown in Figure 4, enables the administrator to configure default access and launch permissions on a machine-wide basis. These settings are used for components that do not provide their own settings. Clicking the Edit Default button presents a list of users and user groups that can be explicitly allowed or denied permission. When the system is first installed, only administrators, the system account, and the interactive user have launch permission. It is generally not recommended to change these values; instead of changing the machine-wide default settings that affect all components, it is preferable to adjust the security settings using
the role-based mechanism described later. The system account is a highly privileged local account used by system proces-
ses. It must always have
access permissions because the Service Control Manager (SCM; rpcss.dll) runs in this account. Configuring Component Identity
|
![]() |
Figure 5 The Identity Tab |
When it is configured to run as the interactive user, the component will be run under the identity of the user currently logged on, which means that the component has access to the interactive desktop visible to the user. The problems with configuring a component for execution as the interactive user are threefold. First, a user must be logged on in order for the component to execute. Second, you never know who will log on, and thus the component might have many rights (if the administrator is logged on) or few rights (if a guest is logged on). Third, if the user logs off while the component is running, the component dies. This option is most useful for a system such as a distributed whiteboard-style drawing application that needs to interact with the user, as well as for debugging purposes. It is not recommended for other types of server or middle-tier components.
The second identity option is to configure the component for execution under a specific user account. When an attempt is made to launch the component, COM+ will automatically initiate a system logon using the specified user account by calling the Win32® API function LogonUser, followed by a call to the CreateProcessAsUser function. As part of the logon procedure, a new, noninteractive window station will be created for use by the component. This setting is often the best option for components that will serve many client programs simultaneously, as all instances of the component will be loaded into one window station. User accounts that are used by COM+ for logon purposes must be assigned the special privilege Log On As a Batch Job or COM+ will not be able to successfully log in using the account. The Component Services administration tool automatically grants this privilege to any user account specified on the Identity tab. Role-based Security
Programmatic Security
In cases which role-based security is disabled, the IsCallerInRole method always returns true, which can lead the component to grant permissions to ineligible users. To overcome this problem, the IObjectContext::IsSecurityEnabled method can be called to determine whether role-based security is currently being enforced by COM+. Thus, the method shown earlier might be rewritten to call the IsSecurityEnabled function as follows:
For components requiring greater control over the security model than offered by declarative security and the IsSecurityEnabled and IsCallerInRole methods of the IObjectContext interface, the context object also implements the ISecurityProperty interface. a COM+ object can use the methods of the ISecurityProperty interface to obtain precise information about the identity of its caller stored in the context object. The ISecurityProperty interface is defined in IDL notation like so: |
|
Notice that all the methods of the ISecurityProperty interface work with a security identifier (SID), a unique value that identifies a specific user or user group. Because they specifically identify a unique user, SIDs do not have the flexibility of the role-based security promoted by COM+. Once an SID is obtained from a method of the ISecurityProperty interface, the COM+ object can use this value when calling the security functions of the Win32 API. In this way, the richness and complexity of the Windows security model is available to configured components. Components written in Visual Basic® do not have much use for SIDs, and are instead provided with the user name identified by the SID. The CoInitializeSecurity Function
The first parameter of CoInitializeSecurity, pSecDesc, is declared as a PSECURITY_ DESCRIPTOR, which is simply a pointer to void. This polymorphic argument defines the component's access permissions in one of three ways. Typically, pSecDesc points to a Win32 security descriptor that COM+ will use to check access permissions on new connections. The pSecDesc parameter can also point to a globally unique identifier (GUID) that references an AppID in the registry where declarative security information is stored, or it can point to an implementation of the IAccessControl inter-face. Figure 6 shows the three different ways in which the polymorphic pSecDesc parameter can be used to perform access control.
|
![]() |
Figure 6 Three Access Control Options Using pSecDesc |
CoInitializeSecurity interprets the pSecDesc parameter based on the value of the dwCapabilities parameter. If the dwCapabilities parameter contains the EOAC_APPID flag, then pSecDesc must point to a GUID of an AppID in the registry. In this case, COM+ obtains all the security settings from the registry and all other parameters of the CoInitializeSecurity function are ignored. If the EOAC_ APPID flag is set in the dwCapabilities parameter but the pSecDesc parameter is NULL, CoInitializeSecurity looks for the EXE of the process in the HKEY_CLASSES_
ROOT\AppID section of the registry and uses the AppID stored there. This behavior is identical to the default behavior obtained when you allow COM+ to call CoInitializeSecurity automatically.
If the EOAC_ACCESS_CONTROL flag is set in the dwCapabilities parameter, then CoInitializeSecurity interprets pSecDesc as a pointer to a COM+ object that implements the IAccessControl interface. COM+ will call this implementation of IAccessControl to determine access permissions at runtime. If neither the EOAC_APPID nor EOAC_ACCESS_CONTROL flag is set in the dwCapabilities parameter, then CoInitializeSecurity interprets pSecDesc as a pointer to a Win32 security descriptor structure that will be used for access checking. If pSecDesc is null, then no ACL checking will be performed. The second parameter of CoInitializeSecurity, cAuthSvc, specifies the number of authentication services being registered. Zero means that no authentication services are being registered and the process will not be able to receive secure calls; a value of -1 instructs COM+ to choose which authentication services to register. The third parameter, asAuthSvc, is a pointer to an array of SOLE_AUTHENTICATION_ SERVICE structures, each of which identifies one authentication service to be registered. If -1 was passed as the cAuthSvc parameter (to instruct COM+ to choose the authentication services), then the asAuthSvc parameter must be null. The definition of the SOLE_AUTHENTICATION_SERVICE structure is: The first field of the SOLE_AUTHENTICATION_SERVICE structure, dwAuthnSvc, specifies which authentication service should be used to authenticate client calls; this field can be set to one of the constants shown in Figure 7. The authentication service specified by CoInitializeSecurity determines which security providers are used for incoming calls; outgoing calls may use any security provider installed on the machine.
The second field of the SOLE_AUTHENTICATION_SERVICE structure, dwAuthzSvc, indicates the authorization service to be used by the server. The RPC_C_AUTHN_WINNT and RPC_C_AUTHN_GSS_KERBEROS authentication packages do not utilize an authorization service, and therefore this field must be set to RPC_C_AUTHZ_NONE when you are using NTLM or Kerberos authentication. The third field of the structure, pPrincipalName, defines the principal name to be used with the authentication service. The last field, hr, contains the HRESULT value, indicating the status of the call to register this authentication service. If the asAuthSvc parameter is not null and CoInitializeSecurity is unable to successfully register any of the authentication services specified in the list, then the RPC_E_NO_GOOD_SECURITY_PACKAGES error is returned. You should check the SOLE_AUTHENTICATION_ SERVICE.hr attribute for error codes specific to each authentication service. Authentication and Impersonation Levels
|
![]() |
Figure 8 Authentication Structures |
The seventh parameter, pAuthList, must be set to null on Windows NT 4.0-based systems. In Windows 2000, the pAuthList parameter points to a SOLE_AUTHENTICATION_LIST structure, which contains a pointer to an array of SOLE_AUTHENTICATION_INFO structures, as shown in Figure 8. This list contains the default authentication information to use with each authentication service. Each SOLE_AUTHENTICATION_INFO structure identifies an authentication service (dwAuthnSvc, one of the RPC_C_AUTHN_LEVEL_xxx flags), authorization service (dwAuthzSvc, another one of the RPC_C_IMP_LEVEL_xxx flags), and a pointer to authentication information (pAuthInfo) whose type is determined by the type of authentication service.
For the NTLM and Kerberos security packages, this value points to the SEC_WINNT_AUTH_IDENTITY_W structure containing the user name and password. For Snego, the pAuthInfo parameter should either be null or point to a SEC_WINNT_ AUTH_IDENTITY_EXW structure, in which case the structure's PackageList member must point to a string containing a comma-delimited list of authentication packages, and the PackageListLength member should contain the number of bytes in the PackageList string. If pAuthInfo is null, Snego will automatically pick a number of authentication services to try from those available on the client machine. The client specifies these values in the call to CoInitializeSecurity. When COM+ negotiates the default authentication service for a proxy, it uses the default information specified in the pAuthInfo parameter for that authentication service. If the pAuthInfo parameter for the desired authentication service is null, COM+ will use the process identity to represent the client. Applications that don't fill in the SEC_WINNT_AUTH_IDENTITY_W structure can simply set the pAuthInfo pointer to COLE_DEFAULT_AUTHINFO (-1). The eighth parameter, dwCapabilities, can be used to set additional client-side and server-side capabilities. This value can be composed of a combination of the values from the EOLE_AUTHENTICATION_CAPABILITIES enumeration in Figure 9. Security Blanket Negotiation
The COAUTHINFO Structure
|
![]() |
Figure 10 COSERVERINFO Structure Definitions |
The first two parameters of the COAUTHINFO structure, dwAuthnSvc and dwAuthzSvc, specify which authentication and authorization services should be used to authenticate the client. dwAuthnSvc can be set to one of the RPC_C_AUTHN_xxx flags shown in Figure 7.
The third parameter of the COAUTHINFO structure, pwszServerPrincName, points to a string indicating the server principal name to use with the authentication service. If you are using the NTLMSSP authentication service, the principal name is ignored. If you are using the Kerberos authentication service, then the principal name specified should be the name of the machine account on which you are launching the component. The fourth and fifth parameters of the COAUTHINFO structure, dwAuthnLevel and dwImpersonationLevel, specify the authentication and impersonation levels. These fields can be set to one of the progressively higher levels of authentication and impersonation shown in Figure 2 and Figure 3. Typically, the impersonation level must be set to at least RPC_C_IMP_ LEVEL_IMPERSONATE because the system needs an impersonation token to create a process on behalf of the client. If you are using Kerberos authentication and specify the impersonation level RPC_C_IMP_LEVEL_DELEGATE, then the machine account named by the pwszServerPrincName property must have the "Computer is trusted for delegation" setting configured in the Active Directory™ directory service. The last parameter of the COAUTHINFO structure, dwCapabilities, defines flags that indicate further capabilities of the proxy. Currently, no capability flags are defined and so this flag must be set to EOAC_NONE. The sixth parameter of the COAUTHINFO structure, pAuthIdentityData, points to a COAUTHIDENTITY structure that establishes the identity of the client. The COAUTHIDENTITY structure allows you to pass a particular user name and password to COM+ for the purpose of authentication. The User field specifies the user name, the Domain field specifies the domain or workgroup to which the user belongs, and the Password field contains the user's password. The Flags field specifies whether the strings are stored in Unicode (SEC_WINNT_AUTH_ IDENTITY_UNICODE) or ASCII (SEC_WINNT_AUTH_ IDENTITY_ANSI). Since all COM+ functions work with Unicode strings, the Flags field must be set to SEC_WINNT_AUTH_IDENTITY_UNICODE. The corresponding string length fields indicate the string length minus the terminating null character. When, as is typical, the COAUTHINFO pointer in the COSERVERINFO structure passed to CoCreateInstanceEx and company is set to NULL, COM+ uses the default values for the COAUTHINFO structure based on the default machine security configured in the registry. Figure 11 shows how a client process can use the COAUTHINFO and COAUTHIDENTITY structures to specify its activation credentials when using the CoCreateInstanceEx function to instantiate an object on a remote machine. The IServerSecurity Interface
Using the IServerSecurity interface pointer, the server can call any of the four methods of the interface. To make this easier, COM+ provides several helper functions that call CoGetCallContext to obtain the IServerSecurity interface pointer, call one of its methods, and then release the interface pointer. The helper functions are listed in Figure 13, along with their interface method counterparts.
The IServerSecurity::QueryBlanket method is used by the server to find out about the client that has invoked the current method. This technique can be useful for determining the security credentials of the client and then taking special action that depends on the user identity of the client process. Figure 14 uses the QueryBlanket method to obtain and display information about the client's security blanket. Implementations of the IUnknown::QueryInterface method must never perform access control checking. COM+ requires an object that supports a particular interface identifier (IID) to always return success when queried for that IID. Besides, checking access permissions in QueryInterface does not provide any real security. If client a has a legal ISum interface pointer to a component, it can hand that interface pointer to client B without any calls back to the component. Additionally, COM+ caches interface pointers and does not necessarily call the component's QueryInterface method for each client call. Impersonating the Client
Cloaking
The IClientSecurity Interface
The IClientSecurity::CopyProxy method is used to make a private copy of the proxy. If you call IClientSecurity::SetBlanket on an interface pointer, the security settings will affect all other code in the client process using that interface pointer as well. To limit the scope of the security settings, the client can make a copy of the proxy before adjusting the security blanket. In this way, the client receives a pointer to another proxy through which the object can be invoked. Adjusting the security settings for this proxy does not affect any other code running in the client process.
Obtaining a pointer to the proxy-supplied implementation of the IClientSecurity interface is as simple as calling the IUnknown::QueryInterface method, as shown here: This interface will always be available for cross-apartment calls using standard or type library marshaling. If the IUnknown::QueryInterface call for IClientSecurity fails, the object is either in-process or custom marshaled. Custom marshalers can implement the IClientSecurity interface for consistency if necessary.Note that the IClientSecurity::SetBlanket method returns an error if you set the EOAC_SECURE_REFS, EOAC_ACCESS_CONTROL, or EOAC_APPID flags in the dwCapabilities parameter. These settings are valid for use only when you are calling CoInitializeSecurity. As with the IServerSecurity interface, COM+ provides several helper functions that assist in calling the methods of the IClientSecurity interface. These functions are shown in Figure 16, along with their equivalent methods. Conclusion
|
![]() For related information see: Security in COM+ at: http://msdn.microsoft.com/library/psdk/cossdk/pgservices_security_5jbz.htm. Also check http://msdn.microsoft.com for daily updates on developer programs, resources and events. |
From the November 1999 issue of Microsoft Systems Journal.
|