As it does for many other aspects of COM+, the registry contains a great deal of information relating to the COM+ security model. You can control many, although not all, of the COM+ security settings by setting various options in the registry. You can also configure security settings programmatically, but there are decided advantages to using the registry. By manipulating the registry, a knowledgeable system administrator can flexibly configure and customize the security environment. But the best thing about configuring security settings in the registry is that COM+ enforces all of these settings automatically. This technique reduces the amount of security-related code you need to write—a definite plus, in our opinion. For example, you can specify that a user named "Mary" or users belonging to the "Accountants" group are not permitted to launch or access a particular component. You need never worry that Mary or Accountants will be able to use the component.
The easiest place to begin exploring the COM+ security model is the Distributed COM Configuration utility. You use this powerful tool to set component and security settings in the registry. In the first half of this chapter, we'll explore the options that this utility makes available and where in the registry these settings are stored. Note that there is no security risk associated with storing security information in the registry. Like other parts of the Windows system, the registry is fully securable and permits only privileged users to access and modify sensitive areas. In fact, only administrators are permitted to run the utility on Windows NT and Windows 2000. Of course, there are also certain disadvantages to using the registry to configure security information, but we'll save these for later in the chapter.
The declarative security information stored in the registry can be neatly divided into two arenas: default security and component security. Default security settings are for all components running on the local machine that do not in some way override these default settings. Component security settings can provide special security for a specific component, thereby overriding the default security settings.1 Let's begin by examining the default security settings.
Launching the Distributed COM Configuration utility and clicking the Default Properties tab displays the options shown in Figure 18-2. The administrator can use these options to set the default authentication and impersonation options on a machinewide basis. The Enable Distributed COM On This Computer check box is the master switch of COM+. If this check box is deselected, all remote calls to and from the machine are rejected. When the system is first installed, this check box is selected. The Enable COM Internet Services On This Computer check box determines whether COM+ Internet Services (CIS) is available; by default, it's disabled. Select this check box if you're planning to use COM+ over the Internet, and then see the section titled "Internet Services" in Chapter 19.
Figure 18-2. The Default Properties tab of the Distributed COM Configuration utility.
The Default Authentication Level setting specifies the base authentication level for the system, assuming that a component does not override the value programmatically or through other registry 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 the following table. Although Windows 95 and Windows 98 machines can make calls at any authentication level, they can only receive calls made at the RPC_C_AUTHN_LEVEL_NONE or RPC_C_AUTHN_LEVEL_CONNECT levels. Note that datagram transports, such as User Datagram Protocol (UDP), default to packet-level authentication if a lower authentication level is requested, which is logical because datagram transports do not maintain a virtual connection between the client and the server. Therefore, each transmitted packet should be authenticated individually.
Value | Authentication Level | Flag | Description |
---|---|---|---|
0 | Default | RPC_C_AUTHN_LEVEL_DEFAULT | The authentication level is chosen automatically using the normal COM+ security blanket negotiation algorithm. |
1 | None | RPC_C_AUTHN_LEVEL_NONE | No authentication. |
2 | Connect | RPC_C_AUTHN_LEVEL_CONNECT | Authenticates the client only when the client first connects to the server. |
3 | Call | RPC_C_AUTHN_LEVEL_CALL | Authenticates the client at the beginning of each remote call. |
4 | Packet | RPC_C_AUTHN_LEVEL_PKT | Authenticates that all of the data received is from the expected client. |
5 | Packet Integrity | RPC_C_AUTHN_LEVEL_PKT_INTEGRITY | Authenticates data integrity and verifies that it has not been modified when transferred between the client and the server. |
6 | Packet Privacy | RPC_C_AUTHN_LEVEL_PKT_PRIVACY | Authenticates, verifies, and encrypts the arguments passed to every remote call. |
The Default Impersonation Level setting specifies the base impersonation level that clients running on this system grant to their servers, again assuming that a component does not override this value. Impersonation levels 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. 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 the table below. Note that Windows NT supports only the RPC_C_IMP_LEVEL_IDENTIFY and RPC_C_IMP_LEVEL_IMPERSONATE impersonation levels; Windows 2000 also supports the RPC_C_IMP_LEVEL_DELEGATE impersonation level when the Kerberos security protocol is used.
The Provide Additional Security For Reference Tracking check box indicates whether calls to the IUnknown::AddRef and IUnknown::Release methods are secured. When the system is first installed, this option is turned off. Selecting this check box causes COM+ to perform additional callbacks to authenticate distributed reference count calls, ensuring that objects are not released maliciously. This option improves the security of the system but slows execution.2
Value | Impersonation Level | Flag | Description |
---|---|---|---|
0 | Default | RPC_C_IMP_LEVEL_DEFAULT | The impersonation level is chosen automatically using the normal security blanket negotiation algorithm of COM+. |
1 | Anonymous | RPC_C_IMP_LEVEL_ANONYMOUS | The client is anonymous to the server. The server cannot obtain the client's identification information, and it cannot impersonate the client.* |
2 | Identify | RPC_C_IMP_LEVEL_IDENTIFY | The server can obtain the client's identification information. The server can impersonate the client for ACL checking, but it cannot access system objects as the client. |
3 | Impersonate | RPC_C_IMP_LEVEL_IMPERSONATE | The server can impersonate the client's security context while acting on behalf of the client. This level of impersonation can be used only to access resources on the server's machine. |
4 | Delegate | RPC_C_IMP_LEVEL_DELEGATE | The server can impersonate the client's security context when making outgoing calls to other servers (on other machines) on behalf of the client. |
* RPC_C_IMP_LEVEL_ANONYMOUS is currently not supported.
The Distributed COM Configuration utility's Default Security tab, shown in Figure 18-3, enables the administrator to configure default access and launch permissions on a machinewide basis. These settings are 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 granted or denied permissions. When the system is first installed, only administrators, the system account, and the interactive user have access and launch permissions. If the default security information is deleted from the registry, only the system account and the interactive user retain permissions. In general, you should avoid changing these values; instead of changing the machinewide default settings that affect all components, it is preferable to adjust the security settings on a component-by-component basis, as described in the next section.
Figure 18-3. The Distributed COM Configuration utility's Default Security tab.
The system account is a highly privileged local account used by system processes. It must always have launch and access permissions because the Service Control Manager (SCM; rpcss.dll) runs in this account. If a component does not grant the system account launch permission, the component can never be launched.
The requirement that the system account have launch permission explains a common source of confusion regarding the CLSID\LocalServer32 registry key. Component launch always fails if the LocalServer32 registry key contains a Universal Naming Convention (UNC) path in the form \\server\share\directory\component.exe, because although the system account has the right to do most anything on the local machine, it has no network privileges.3 This means that the SCM cannot launch an executable component that resides on a remote machine. Keep in mind that UNC paths fail even if they refer to an executable component that resides on the local machine.
In the Default Configuration Permissions area of the Default Security tab, the administrator can control the security of the entire HKEY_CLASSES_ROOT section of the registry. These settings can be used to restrict ordinary users from being able to view or modify the contents of this crucial area in the registry. To view or change the security settings of individual registry keys, you use the old registry editor (regedt32.exe) instead of the new one (regedit.exe). Note that some legacy components are designed to refresh their registry settings every time they run.4 Refreshing the registry settings can cause components to fail if you restrict write access to the registry. Therefore, it is recommended that modern components do not refresh their entries in the HKEY_CLASSES_ROOT section of the registry every time they are executed.
In Windows 95 and Windows 98, only the default access permissions are configurable; all the other settings are not applicable because of the limited security available on these platforms. However, an additional option, Enable Remote Connection, is available. This option determines whether remote clients are permitted to connect to objects running on the local computer. Although Windows 95 and Windows 98 do not support remote launching of components, they do permit connections to running objects. When the system is first installed, however, this option is disabled; you enable it using the Distributed COM Configuration utility or another registry editor.
All of the options presented on the Default Properties and Default Security tabs of the Distributed COM Configuration utility are controlled by registry values stored in the HKEY_LOCAL_MACHINE\Software\Microsoft\OLE key. The following table describes the named-values that can be stored in this registry key.
HKEY_LOCAL_MACHINE\Software\Microsoft\OLE Values | Description |
---|---|
DefaultLaunchPermission | Specifies the default ACL that determines who can launch components |
DefaultAccessPermission | Specifies the default ACL that determines who can access components |
EnableDCOM | Specifies whether Distributed COM is enabled on this machine |
EnableRemoteConnect | Specifies whether objects running on the machine accept remote connec- tions; by default, it is set to no (N)* |
LegacyAuthenticationLevel | Sets the default authentication level |
LegacyImpersonationLevel | Sets the default impersonation level |
LegacySecureReferences | Specifies whether AddRef and Release method calls are secured |
LegacyMutualAuthentication | Specifies whether mutual authentication is enabled** |
* Windows 95 and Windows 98 only.
** Windows 2000 only.
So far, we've explored how you can use the Distributed COM Configuration utility to set the registry entries that control the default COM+ security settings. In this section, we'll examine the registry settings that control security on a per-component basis. For components with more specialized security requirements, you can use these settings to override the machinewide default settings.
The AppID registry key was designed to group the configuration options for one or more objects housed by a component into one centralized location in the registry. A unique class identifier (CLSID) is assigned to each coclass, but all coclasses housed by one component must map to the same application identifier (AppID).5 The following table describes the four security-related values that can be stored in an AppID registry entry. These four named values correspond precisely to the four areas of the COM+ security model: launch, access, authentication, and identity control.
Security-Related AppID Values | Description |
---|---|
LaunchPermission | Specifies the ACL that determines who can launch the component |
AccessPermission | Specifies the ACL that determines who can access the component |
AuthenticationLevel | Sets the authentication level to be used for the component |
RunAs | Sets the user account in which the component will execute |
When you run the Distributed COM Configuration utility, it presents a list of components registered on the local machine. To configure the security settings for a particular component, you select it from the list and click the Properties button. Then you click the Security tab and specify the launch and access permissions for a specific component. These options affect the AccessPermission and LaunchPermission values in the component's AppID registry key. You select the Use Default Access Permissions and Use Default Launch Permissions options to instruct COM+ to use the machinewide default security settings for this component. Otherwise, select Use Custom Access Permissions or Use Custom Launch Permissions and click the corresponding Edit button to control the security for this specific component, as shown in Figure 18-4.
Recall that when the system is first installed, the machinewide default launch and access permissions enable only administrators, the system account, and the interactive user to launch and access components. We recommend that you not change these machinewide settings. However, because remote clients are not likely to be logged on as administrators, you'll typically modify the component's launch and access security to grant or deny permissions to everyone, various user groups, or even specific users. This technique is preferable to adjusting the machinewide default security settings that affect all components. Granting launch and access permissions to all users (Everyone) and setting the authentication level to none (RPC_C_AUTHN_LEVEL_NONE) enables anonymous activation and access.6
Figure 18-4. Using the Distributed COM Configuration utility to set the access permissions for a component.
Although large parts of the COM+ security model, such as authentication and impersonation, are platform-independent through the use of the SSPI, until recently access control was performed exclusively using Windows DACLs. The Win32 API includes functions that can create and manipulate ACLs. These ACLs can then be serialized into the registry keys that control COM+ security, allowing you to configure launch and access permissions. However, because ACLs created in Windows NT or Windows 2000 are obviously not portable (they aren't even supported in Windows 95 and Windows 98), Microsoft needed a platform-independent solution for the COM+ security model. That solution is the IAccessControl interface, shown below in IDL notation.
interface IAccessControl : IUnknown { // Merge the new rights with the existing access rights. HRESULT GrantAccessRights([in] PACTRL_ACCESSW pAccessList); // Replace the existing access rights with the new rights. HRESULT SetAccessRights([in] PACTRL_ACCESSW pAccessList); // Set an item's owner or group. HRESULT SetOwner( [in] PTRUSTEEW pOwner, [in] PTRUSTEEW pGroup); // Remove explicit entries for the list of trustees. HRESULT RevokeAccessRights( [in] LPWSTR lpProperty, [in] ULONG cTrustees, [in, size_is(cTrustees)] TRUSTEEW prgTrustees[]); // Get the entire list of access rights. HRESULT GetAllAccessRights( [in] LPWSTR lpProperty, [out] PACTRL_ACCESSW_ALLOCATE_ALL_NODES* ppAccessList, [out] PTRUSTEEW* ppOwner, [out] PTRUSTEEW* ppGroup); // Determine whether a trustee has access rights. HRESULT IsAccessAllowed( [in] PTRUSTEEW pTrustee, [in] LPWSTR lpProperty, [in] ACCESS_RIGHTS AccessRights, [out] BOOL* pfAccessAllowed); } |
Although it is possible to write security code that uses the Win32 API security functions, doing so unnecessarily ties a component to the Windows platform. Component developers who want to write portable code that works on Windows as well as on other platforms that support COM+ should use the IAccessControl interface instead. The true goal of the IAccessControl interface is to provide programmatic security, but we'll first explore its use in creating platform-independent access control information stored in the registry.
Although it is considered wiser to let the administrator configure the security settings using the Distributed COM Configuration utility, it might occasionally be advantageous to control the registry settings from within a component. Relying on an administrator to configure critical security settings can sometimes be a mistake. In such cases, configuring the registry-based security settings for a component might be a necessary part of the installation program. Of course, the administrator could later adjust these settings using the Distributed COM Configuration utility.
Windows provides an implementation of the IAccessControl interface in the CLSID_DCOMAccessControl coclass. In addition to implementing the IAccessControl interface, this system object also implements the IPersist and IPersistStream interfaces, which you can use to configure an ACL using the methods of the IAccessControl interface. You can then store these settings in a registry key that COM+ will use to determine access and launch permissions. The Distributed COM Configuration utility itself uses the DCOMAccessControl object to create these registry entries in Windows 95 and Windows 98, where the Win32 API security functions are not available, while the Windows NT and Windows 2000 versions of this utility use the Win32 API security functions to put this information in the registry.7
The ACTRL_ACCESS structure, which is used as a parameter by several methods of the IAccessControl interface, is somewhat complicated. It has several levels of structures nested within one another, as shown in Figure 18-5. Aficionados of the Win32 API security functions will recognize the TRUSTEE structure buried deep within the ACTRL_ACCESS structure. The IAccessControl interface has borrowed the TRUSTEE structure from the security functions of the Win32 API. A trustee identifies a security principal, which can be a user account, a group account, or a logon session. The TRUSTEE structure enables you to use a name (ptstrName) or an SID (pSid) to identify a trustee. When you work within the COM+ security model, it is certainly preferable to identify trustees by name instead of a Windows-specific SID. The Windows NT and Windows 2000 implementations of the DCOMAccessControl object automatically look up the SID that corresponds to the trustee name.
Figure18-5. The structures nested in the ACTRL_ACCESS structure.
The following sample program shows how to use the DCOMAccessControl object to read and display COM+ security information stored in registry entries in a platform-independent manner. The numbers shown in parentheses here correspond to the numbered elements shown in boldface in the code. The program calls CoCreateInstance to instantiate the CLSID_DCOMAccessControl coclass and obtain a pointer to its IPersistStream interface (1). Then the IPersistStream::Load method is called to initialize the object with the security information stored in the AppID\AccessPermission registry value of a component (2). Next, the program calls the IUnknown::QueryInterface method to request a pointer to the IAccessControl interface (3) for use in calling the IAccessControl::GetAllAccessRights method (4). Not shown in this code is a loop that displays all of the trustees enumerated in the ACL obtained from the GetAllAccessRights method. The companion CD also includes a program, based on a sample from the Microsoft Platform SDK, that illustrates how to write security settings to the registry using the CLSID_DCOMAccessControl implementation of the IAccessControl interface.
#include <windows.h> #include <stdio.h> #include <iaccess.h> // For IAccessControl // Need to define this ourselves! const IID IID_IAccessControl = {0xEEDD23E0,0x8410,0x11CE, {0xA1,0xC3,0x08,0x00,0x2B,0x2B,0x8D,0x8F}}; // This is the header stored in the registry. typedef struct { WORD version; WORD pad; GUID classid; } SPermissionHeader; void main() { HRESULT hr = CoInitialize(NULL); // Open the AppID key. // Replace the AppID below with an AppID for which you // have configured custom access permission. HKEY key = 0; hr = RegOpenKeyEx(HKEY_CLASSES_ROOT, "AppID\\{10000002-0000-0000-0000-000000000001}", 0, KEY_READ, &key); // Read the value from the registry. DWORD dwSize = 0; hr = RegQueryValueEx(key, "AccessPermission", NULL, NULL, NULL, &dwSize); void* pMemory = (void*)CoTaskMemAlloc(dwSize); hr = RegQueryValueEx(key, "AccessPermission", NULL, NULL, (unsigned char*)pMemory, &dwSize); IStream* pStream; hr = CreateStreamOnHGlobal(pMemory, TRUE, &pStream); // (1) Create an instance of the // CLSID_DCOMAccessControl object. IPersistStream* pPersistStream = NULL; hr = CoCreateInstance(CLSID_DCOMAccessControl, NULL, CLSCTX_INPROC_SERVER, IID_IPersistStream, (void**)&pPersistStream); LARGE_INTEGER size; size.QuadPart = sizeof(SPermissionHeader); hr = pStream->Seek(size, STREAM_SEEK_SET, NULL); // (2) Initialize the CLSID_DCOMAccessControl object with // the data from the registry. hr = pPersistStream->Load(pStream); // (3) Request a pointer to the IAccessControl interface. IAccessControl* pAccessControl = NULL; hr = pPersistStream->QueryInterface(IID_IAccessControl, (void**)&pAccessControl); ACTRL_ACCESSW* pAccess = NULL; TRUSTEEW* pOwner = NULL; TRUSTEEW* pGroup = NULL; // (4) Call the IAccessControl::GetAllAccessRights method. hr = pAccessControl->GetAllAccessRights(NULL, &pAccess, &pOwner, &pGroup); // Program proceeds to enumerate all security principals // returned by IAccessControl::GetAllAccessRights. // Release everything and bail out. CoTaskMemFree(pAccess); CoTaskMemFree(pOwner); CoTaskMemFree(pGroup); pPersistStream->Release(); pStream->Release(); pAccessControl->Release(); RegCloseKey(key); CoUninitialize(); } |
The Identity tab of the Distributed COM Configuration utility for a selected component, shown in Figure 18-6, enables the administrator to determine which user account the component will execute in. The Identity tab provides three options for defining the user account: The Interactive User, The Launching User, and This User. Changes made on the Identity tab affect the RunAs value in the component's AppID registry key.
Figure 18-6. The Identity tab of the Distributed COM Configuration utility for a selected component.
The default identity setting for components is that of the launching user. With this setting, the RunAs value is not present in the component's AppID registry key. Running as the launching user means that the component executes under the security credentials of the client process, which is somewhat analogous to using impersonation for the lifetime of the component. Each distinct client gets a new instance of the server, and each server runs in its own window station. This happens regardless of whether the coclass used the REGCLS_MULTIPLEUSE flag when calling the CoRegisterClassObject function. Because each client has its own instance of the server, this is the most secure setting but also the most resource-intensive one. Imagine a thousand users connecting to a component and each one causing a new window station and process to be created! Also note that components launched under the identity of the launching user do not have access to the interactive desktop visible to the end user.
In Windows NT, components that are run as the launching user receive somewhat crippled security credentials because the system does not support delegation-level impersonation. For example, remote calls to other machines cannot be made when the component runs under the identity of the launching user; nor can the component access files be shared across the network. If this access were allowed, impersonation could form an endless chain from one computer to another, allowing a rogue component to assume the client's security credentials and do all sorts of bad things with them.
In Windows 2000, delegation-level impersonation is supported via the Kerberos security provider. The Kerberos security protocol has built-in safeguards that limit what can be done with delegation-level impersonation. However, the launching user setting is a special case when you use Kerberos authentication. Because the server's secret key is not available to decrypt the service ticket presented by the client, "user-to-user" authentication must be employed. This means that the client must use Kerberos to activate the server at delegate level impersonation in the CoCreateInstanceEx call.8
When a component is configured to run as the interactive user, it runs under the identity of the currently logged-on end user, which means that it has access to the interactive desktop visible to the user. This configuration has three problems.
First, a user must be logged on in order for the component to execute. Second, you never know who will log on, so 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 configuration 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 RunAs registry value is set to Interactive User for components configured to run under the identity of the end user.
The third identity option is to configure the component for execution under a specific user account. When an attempt is made to launch the component, COM+ automatically initiates 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 is created for use by the component. This setting is often the best option for components that will serve many client programs simultaneously because all instances of the component will be loaded into one window station. In addition, if the class was registered with the REGCLS_MULTIPLEUSE flag, multiple clients will be able to share access to a single instance of the component.
The RunAs value in the component's AppID registry key is set to a string in the form domain\user, and the password is stored in a secure part of the registry managed by the Local Security Authority (LSA) subsystem of Windows. The LSA is a protected subsystem used to maintain information about all aspects of local security on a system; only an administrator can use the LSA API functions to read and write the secured password information stored in the registry.
Note that components registered to run under a specific account will always fail if you attempt to run them manually under the guise of a different user account. Suffering from an identity crisis, the CoRegisterClassObject function will return the error CO_E_WRONG_SERVER_IDENTITY. This error occurs because the code calling CoRegisterClassObject can't be trusted. Only the securable registry settings that specify which component to run and under what identity can be trusted. This restriction prevents a malicious component from spoofing client programs by masquerading as an upstanding class object with a registered CLSID.
User accounts that COM+ uses for logon purposes must be assigned the special right Log On As A Batch Job. Otherwise, COM+ cannot successfully log on using the account. The Distributed COM Configuration utility automatically grants this right to any user account specified on the Identity tab. It can do this because the user of this utility must be an administrator and therefore has the right to confer special privileges on other users. Note that the identity of components implemented as services is not configured using the Distributed COM Configuration utility or the RunAs registry value. Instead, the LocalService value of the AppID registry key names the service, and the identity is configured in the Services section of the Component Services management console.