Deployment of Managed COM Add-Ins in Office XP

Click here to download sample - odc_shim.exe. This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

Misha Shneerson
Siew-Moi Khor
Microsoft Corporation

May 2002

Applies to:
     Microsoft® Office XP

Summary: How to consider security pertaining to the installation and deployment of managed COM add-ins and smart tags in Office XP. Possible solutions to ensure strict Office XP security conformity are also presented and discussed. (15 printed pages)

Download Odc_shim.exe.

Contents

Introduction
Concepts and Terminology
Security in Office XP
How Managed Code Is Different
The Solution: A Dedicated Unmanaged Shim Component
Implementing Proxy Objects
Guide in Testing Your Solution
Deploying Your Solution
Next Steps

Introduction

By providing a Component Object Model (COM) Interop layer and COM Interop tools, Microsoft has made the transition of applications from the COM world to the .NET world as seamless as possible. You can implement COM (unmanaged) interfaces in .NET (managed code) and use those implementations transparently from COM-based clients.

If you want to build custom managed COM add-ins in Microsoft® Office XP, there are three interfaces that would be of special interest to you: the IDTExtensibility2, IsmartTagRecognizer, and ISmartTagAction interfaces. The IDTExtensibility2 interface is implemented when building custom COM add-ins, and the ISmartTagRecognizer and ISmartTagAction interfaces are implemented when building custom smart tag COM add-ins.

In this article we will discuss the installation and deployment of managed COM add-ins in Office XP. Unless otherwise indicated, we will use the term COM add-ins to mean COM add-ins that implement the IDTExtensibility2, IsmartTagRecognizer, or ISmartTagAction interfaces. It will be obvious from the context of a sentence when we make a distinct differentiation between smart tag COM add-ins and COM add-ins that only implement the IDTExtensibility2 interface.

When a managed COM add-in is deployed using the register assembly (regasm) utility and the security setting in an Office XP application is set to Medium with Trust all installed add-ins and templates disabled, the user will be prompted to either enable or disable the COM add-in when the application is launched. However, if the security level is set to High with Trust all installed add-ins and templates disabled, your managed COM add-in won't be loaded even when it is signed. (If you are unfamiliar with the security setting options in Office XP, please refer to Table 1 below).

This isn't the behavior you would expect in correspondence to the security settings in Office XP. And you are right—if the COM add-in installed is an unmanaged COM add-in. However, with managed COM add-ins, to properly and correctly deploy them, there is an additional step that a developer needs to take to ensure compliance with the Office security model. A proper and correct deployment of managed COM add-ins means the managed COM add-ins are digitally signed and the security settings in Office XP applications are set to its highest.

The installation and deployment of managed COM add-ins need special security considerations and handling. To ensure a successful deployment of digitally signed managed COM add-ins you will need to incorporate into your managed COM add-in project a small unmanaged proxy called a shim.

This article will discuss why a shim is needed and how the shim solution works. How to use the shim solution will be addressed in two upcoming step-by-step tutorials: one for managed smart tag COM add-ins and another for generic managed COM add-ins:

The step-by-step tutorials will include the downloadable shim solution template.

It is assumed that the reader is familiar with Microsoft .NET technology and knows how to build a managed COM add-in using Microsoft Visual Studio® .NET. It's further assumed that the reader knows what code signing is and how to sign code. If not, there are many excellent articles written on those topics on MSDN—a list of reference is given at the end of the article.

Concepts and Terminology

Before diving into the issue, a number of concepts and terminologies are briefly discussed here. To fully grasp the security issues surrounding managed COM add-ins in Office XP, understanding these concepts will help greatly. These concepts are laid out in a logical manner. If you know the following concepts well, please feel free to skip this section. For more detailed information on these concepts, please refer to the articles listed in the references section at the end of this article.

Assembly, Microsoft Intermediate Language (MSIL), and Common Language Runtime

An assembly is analogous to a COM dynamic-link library (DLL) or executable (EXE) file. It's the logical unit where compiled code targeted at .NET is stored. Compiling a Microsoft Visual Basic® .NET or Microsoft Visual C#™ class library project produces an assembly containing the Microsoft intermediate language (MSIL) code. The execution of the MSIL code is handled by the common language runtime. The common language runtime is different from other execution environments; to achieve optimal performance, the common language runtime compiles the MSIL into machine code. This machine code then runs without the major overhead of an interpreter.

Digital Signature

Digital signatures are used to verify if an assembly has been tampered with. Public-key cryptographic protocols and techniques are used in digital signature verification. A discussion about public-key cryptography is beyond the scope of this article.

However, of special interest with regards to digital signatures are two very large numbers known as the public and private key pairs. The keys complement each other in the following way—data signed using the private key can only be verified successfully with the corresponding public key. Typically, software publishers use their private key to sign their assembly before distributing an assembly to the public over the network. The public key is appended to the assembly. Before the assembly is allowed to execute, the common language runtime checks if the data integrity of the assembly has been compromised. If the assembly has been tampered with, the digital signature cannot be verified with the public key and therefore the assembly won't be allowed to execute. If the verification went successfully, only then the assembly is loaded and allowed to run.

Strong Name and Microsoft Authenticode

To ensure data integrity and authenticity of an assembly, strong names are used. Strong names are created using the strong name utility and implemented using public-key cryptography. Strong names ensure shared assemblies have globally unique names; a strong name comprise a simple text assembly name, version number, culture information, public key and digital signature.

Strong names and Microsoft Authenticode® serve different goals. Authenticode, while using public-key cryptographic protocols for data integrity, goes much further. It also uses a hierarchical system of trusted authorities to verify that someone who claims to be a software publisher of a file is its publisher indeed. In particular, Authenticode implies a level of trust associated with a software publisher, while strong names do not.

Strong names are an integral part of an assembly's content and build process. Every strong-named assembly references other assemblies by their strong names. Doing this ensures that at runtime the assembly will use those exact assemblies it was linked with, thus resolving DLL conflicts. On the other hand, Authenticode signature is stored in an assembly's Portable Executable (PE) header and is often used in addition to strong names to establish trust of an assembly. This is also why an assembly must be strong named before it is signed with Authenticode.

.NET Security

.NET security is a huge and fascinating topic. However, its discussion is beyond the scope of this article. For more information about .NET security, An Overview of Security in the .NET Framework is a good place to start.

However, there are a few considerations that need to be called out. When an assembly is loaded, a set of permissions is granted to it. An assembly is said to run in a Full Trust mode when it is granted the maximum set of permissions and can therefore perform any operation it wants. Some assemblies may not be granted a particular set of permissions, and therefore, for example, it may not be able to perform input/output operations or call unmanaged code.

The process of permissions granting is controlled by a machine's common language runtime security policy (which will run under the default settings unless reconfigured by a user or a security policy administrator of an organization).

Security in Office XP

Many security conscious users and administrators set their Microsoft Office XP security level to High, with the default Trust all installed add-ins and templates setting disabled, which is highly recommended. This particular setting will automatically disable all add-ins and templates that are not code signed. And if the add-ins and templates are code signed with a certificate not listed in the Trusted Sources list, a user will be prompted to either enable or disable the add-ins or templates.

Table 1. Office XP Security Setting Options

Security level Trust all installed add-ins and templates Digitally signed From Trusted Sources Office XP will
High Unchecked Yes Yes Load the add-in/macro silently
High Unchecked Yes No Prompt to enable/disable the add-in/macro
High Unchecked No N/A Not load the add-in/macro
Medium Unchecked Yes Yes Load the add-in/macro silently
Medium Unchecked Yes No Prompt to enable/disable the add-in/macro
Medium Unchecked No N/A Prompt to enable/disable the add-in/macro
Low Unchecked Yes/No Yes/No Load the add-in/macro silently
High, Medium, or Low Checked Yes/No Yes/No Load the add-in/macro silently

Note   To check the security settings in an Office XP application, click Tools, point to Macro, and then click Security. This displays the Security dialog box.

As can be seen from the preceding paragraph and Table 1, Office XP approaches security from a trust perspective. Add-ins and templates will only be allowed to run if they are trusted, that is, published by trusted sources or if the user opts to Trust all installed add-ins and templates.

The notion of trust is subjective. It is up to the user to decide whether he/she wants to trust the code published by a particular source. What is objective are the software publisher's identity and the certificate used by the software publisher to sign the code file. Office XP components must use Authenticode to sign their code files so that users of the code files can verify the identity of the software publisher.

Office COM add-ins are COM components contained in native DLL files. When a COM component is registered, some data about it is written into the registry. Specifically, COM components (or COM classes) all have a globally unique identity called class ID (CLSID). All CLSIDs are grouped in the registry under HKEY_CLASSES_ROOT\CLSID and have the following form {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX}, where 'X' is a hexadecimal letter. Under the CLSID key there are several nested registry subkeys. One of the subkeys is InprocServer32 whose default value contains the full path to a DLL file as shown in the CLSID key registry layout in Figure 1.

This is the DLL that contains (or provides access) to the COM class associated with the CLSID. And in accordance with the Office security model, this is the class that has to be signed with Authenticode. Since classes cannot be signed with digital signatures, their immediate container is signed, that is, the DLL file.

Click here for larger image

Figure 1. CLSID InprocServer key entries for an unmanaged code COM class (click thumbnail for larger image)

How Managed Code Is Different

Managed code is different because its execution is managed by the common language runtime. Therefore, calls to COM servers will fail to execute if the DLL path as shown in Figure 1 points directly to the location of an assembly. As discussed in the Assembly, Microsoft Intermediate Language (MSIL), and Common Language Runtime section, the assembly that is in MSIL code, has to be executed by the common language runtime. Hence, there is a need to launch the execution environment—the common language runtime.

This is achieved by placing a special DLL named mscoree.dll, as the default value of InprocServer32. To register an assembly for COM Interop operation, the RegAsm.exe (register assembly) utility is used. This utility registers .NET components type information into the system registry so that COM clients can access it, exposing the managed components to the unmanaged COM environment.

Alternatively, you can set Register for COM Interop property to TRUE on the Build page of the project's properties before compiling the assembly. See Registering Assemblies with COM for more information about RegAsm.exe.

The most interesting part is the default value for the InprocServer32 key (see Figure 2). Instead of the path to a managed code DLL file, it points to the mscoree.dll—the main entry point of the common language runtime engine. (The InProcServer32 key is created by the RegAsm.exe utility earlier. The default value is set to mscoree.dll.)

Click here for larger image

Figure 2. CLSID InprocServer key entries for a managed code COM class exposed through COM Interop (click thumbnail for larger image)

Here is why signing an assembly using Authenticode does not help. Since the InprocServer32 key points to mscoree.dll, Office XP will examine mscoree.dll for the signature and not the assembly itself. Mscoree.dll is a system component and is not signed. As such, when the Office XP security is set to High with Trust all installed add-ins and templates disabled, the mscoree.dll won't be loaded.

The Solution: A Dedicated Unmanaged Shim Component

What if it were possible to create an unmanaged code DLL that has all the abilities of the common language runtime engine but is restricted to load only one specific assembly—the managed code COM add-in that you want loaded? The same way RegAsm.exe puts mscoree.dll in the middle, it is possible to put your own component in the middle so that the InprocServer32 value will point to your DLL.

An unmanaged component can host the common language runtime in its address space as described in detail in Microsoft .NET: Implement a Custom Common Language Runtime Host for Your Managed App.

By ensuring that the hosting application can verify the validity of the assemblies that are loaded into its address space, you can satisfy Office XP security requirement that DLLs are signed. This way the security of the user's computer will not be compromised. This particular component that we will call a shim is an unmanaged component that acts as a proxy.

Briefly, here is how it works.

  1. Integrate the shim DLL with the managed COM add-in using Visual Studio .NET.
  2. Sign the shim DLL.
  3. Regsvr32 the signed shim DLL.
  4. When the COM classes are first instantiated inside an Office XP application, the following set of events happen:
    • The common language runtime is started from within the shim DLL using a hosting interface.
    • An AppDomain (application domain which is analogous to processes in an unmanaged environment) is created that will run the COM add-in assembly.
    • The assembly is then loaded into the above domain.
    • Instances of the managed COM add-in classes are created and the pointers to those interfaces are cached.
    • All the calls made into the unmanaged classes are redirected to their managed counterparts using the cached pointers.

Since the shim DLL itself will be signed with Authenticode, Office XP will allow the loading of the shim DLL into its address space. A word of caution: in "putting a component in the middle", if it isn't done correctly, you could potentially open another area for security attacks. So it is very important that security precautions are taken to ensure the paths to your components and assemblies are correct and cannot be spoofed.

It is important to understand that it is the responsibility of the shim to disallow execution of non-trusted assemblies. Fortunately, it is possible to ensure that the assembly to be loaded is the intended one by specifying the assembly strong name and, especially, its public key token (while keeping the corresponding private key secret).

Implementing Proxy Objects

Here is how a shim is implemented to host a managed COM add-in. The shim is implemented in unmanaged Microsoft Visual C++® in the Visual Studio .NET integrated development environment (IDE). Visual Basic developers might ask if the shim can be implemented in Microsoft Visual Basic 6.0. Currently, the answer is no. One of the reasons is that the unmanaged shim COM add-in needs to be in the same development environment as the managed COM add-in assembly. You can develop an unmanaged Visual C++ project in Visual Studio .NET but not an unmanaged Visual Basic 6 project. Also, code written in Visual C++ has the ability to load the common language runtime that isn't possible in Visual Basic.

Visual Basic developers who are unfamiliar with C++ and Active Template Library (ATL) do not be disheartened. Two separate solutions—one to host generic Office XP managed COM add-ins and another specifically for managed smart tags—have been created that you can download and use. You would not need to create one yourself.

What follows is a technical explanation of the shim solution and even if you are unfamiliar with Visual C++ and ATL terms, it shouldn't be much of a problem. Of great importance, too, is the testing guide further down the article.

Developing a shim for an Office COM add-in is very similar to the way a smart tag shim is implemented. To aid the explanation, both the generic COM add-ins and smart tag shim solutions will be used.

The COM add-in shim is a basic ATL project with a proxy class that implements the IDTExtensibility2 interface. To view the solution in Visual Studio .NET, click on the COMAddinShim.sln file located in the COMAddInShim folder of the download accompanying this article. The smart tag shim is a basic ATL project, with two proxy classes that implement the ISmartTagRecognizer and ISmartTagAction interfaces. To view the solution in Visual Studio .NET, click on the SmartTagShim.sln file located in the SmartTagShim folder.

Let us first look at the FinalConstruct() method. (For the COMAddinShim project, to view the FinalConstruct() method, open the ConnectProxy.cpp file. For the SmartTagShim project, open the STRecognizerProxy.cpp or STActionProxy.cpp file.)

The FinalConstruct() method is called each time the containing class is instantiated. The **FinalConstruct()**method of each of the COM add-ins and smart tag classes uses the unmanaged API to create an instance of a corresponding managed component.

Below is a code snippet from the ConnectProxy.cpp file showing how this is achieved:

HRESULT CConnectProxy::FinalConstruct()
{
    HRESULT hr = S_OK;

    CCLRLoader *pCLRLoader = CCLRLoader::TheInstance();
    IfNullGo(pCLRLoader);
    IfFailGo(pCLRLoader->CreateInstance(AssemblyName(),
                                        ConnectClassName(),
                                        __uuidof(IDTExtensibility2),
                                        (void **)&m_pConnect));

Error:
    return hr;
}

The corresponding code snippet from the STRecognizerProxy.cpp file is as shown below:

HRESULT CSmartTagRecognizerShimProxy::FinalConstruct()
{
    HRESULT hr = S_OK;

    CCLRLoader *pCLRLoader = CCLRLoader::TheInstance();
    IfNullGo(pCLRLoader);
    IfFailGo(pCLRLoader->CreateInstance(AssemblyName(),
                                        RecognizerClassName(),
                                        __uuidof(ISmartTagRecognizer),
                                        (void **)&m_pISTRecognizer));

Error:
    return hr;
}

The code is very simple. The bulk of the work happens inside the class called CCLRLoader. The CCLRLoader is a singleton (meaning there is only one instance of the class with a global point of access to it provided) that starts up and actually does all the communication with the common language runtime (the detail implementation of the CCLRLoader is beyond the scope of this article).

The CCLRLoader::CreateInstance() method is used to create an instance of the managed class. It is similar to what CoCreateInstance() does in the COM world. The CCLRLoader::CreateInstance() method accepts the following parameters:

  • The name of the assembly containing the class.
  • The full name of the class to be instantiated.
  • The reference to the identifier of the interface.
  • The address of the output variable that receives the interface pointer.

The values of the first two parameters are returned by the functions from theShimConfignamespace (see the ShimConfig.cpp file). This namespace contains the functions that define the name of the target assembly and the name of the managed class to be instantiated as shown by the code snippet from the COMAddInShim project below:

static LPCWSTR szAddInAssemblyName = 
     L"ManagedAddIn, PublicKeyToken=6c96c7507b8e2be8";
static LPCWSTR szConnectClassName = L"ManagedAddIn.Connect";

LPCWSTR ShimConfig::AssemblyName()
{
    return szAddInAssemblyName;
}

LPCWSTR ShimConfig::ConnectClassName()
{
    return szConnectClassName;
}

The corresponding code snippet from the SmartTagShim project's ShimConfig.cpp file is as follows:

static LPCWSTR szSmartTagsAssemblyName =
    L"HelloSmartTag, PublicKeyToken=6c96c7507b8e2be8";
static LPCWSTR szRecognizerClassName = L"HelloSmartTag.STRecognizer";
static LPCWSTR szActionClassName = L"HelloSmartTag.STAction";

LPCWSTR ShimConfig::AssemblyName()
{
    return szSmartTagsAssemblyName;
}

LPCWSTR ShimConfig::RecognizerClassName()
{
    return szRecognizerClassName;
}

LPCWSTR ShimConfig::ActionClassName()
{
    return szActionClassName;
}

The functions return values are specified at the top part of the code. Note that the path to the managed assembly are not indicated, only its name. The CCLRLoader makes an important assumption, that is, the managed assembly is located in the same directory as the shim DLL.

The AssemblyName () function returns the strong assembly name. In the COM add-in code snippet above, it is "ManagedAddIn, PublicKeyToken=6c96c7507b8e2be8".

The strong name here specifies only the public key token, which is the hash of the public key. It is also possible to specify the version and culture information if it is critical for those to match.

The public key token is very important in preventing a compromised assembly from being loaded. An attacker does not know what the corresponding private key is, hence won't be able to correctly sign an assembly to substitute yours. As such, if an assembly has been tampered with, verification will fail and the assembly won't be allowed to run. It is therefore crucial to remember to never omit the public key token specification since it can create security vulnerability in your software. For information about how to strong-name your assembly, you must refer to the specific instructions for your project type. Right now they are different for C# and Visual Basic .NET projects.

The implementation of the interface methods forCConnectProxy()andCSmartTagRecognizerShimProxy(see ConnectProxy.h and the corresponding STRecognizerProxy.h and STActionProxy.h header files) is straightforward once there is an interface pointer. It redirects the calls to the contained instance of the managed class. The code snippet from the ConnectProxy.h header file of the COMAddInShim project is shown below:

class ATL_NO_VTABLE CConnectProxy :

{
...
public:
    //IDTExtensibility2 implementation:
    STDMETHOD(OnConnection)(IDispatch * Application, 
      AddInDesignerObjects::ext_ConnectMode ConnectMode, IDispatch 
      *AddInInst, SAFEARRAY **custom)
    {
        return m_pConnect->OnConnection(Application, ConnectMode, 
          AddInInst, custom);
    }
    STDMETHOD(OnBeginShutdown)(SAFEARRAY **custom )
    {
        return m_pConnect->OnBeginShutdown(custom);
    }

protected:
    // caches pointer to managed add-in
    AddInDesignerObjects::IDTExtensibility2 *m_pConnect;
};

OBJECT_ENTRY_AUTO(__uuidof(ConnectProxy), CConnectProxy)

The corresponding code snippet from the STRecognizerProxy.h header file is as follows:

class ATL_NO_VTABLE CSmartTagRecognizerShimProxy :
...
{

    // ISmartTagRecognizer Methods
public:
    STDMETHOD(get_ProgId)(BSTR * ProgId)
    {
        return m_pISTRecognizer->get_ProgId(ProgId);
    }

    STDMETHOD(Recognize)(BSTR Text, IF_TYPE DataType, int LocaleID,
      ISmartTagRecognizerSite * RecognizerSite)
    {
        return m_pISTRecognizer->Recognize(Text, DataType, LocaleID,
          RecognizerSite);
    }
protected:
    // caches pointer to managed SmartTagRecognizer
    ISmartTagRecognizer *m_pISTRecognizer;
};

An almost identical code is used to load the managed smart tag action class. The accompanying downloadable code has extensive comments and explains every non-trivial step of the program.

Guide in Testing Your Solution

When testing your solution the following files need to be signed:

  • The shim DLL must be signed with Authenticode.
  • Your COM add-in assembly must be signed with a strong name.
  • Strongly name other private assemblies that the COM add-in assembly might depend on.

In most organizations, the private key used for signing an unmanaged component is accessible only to authorized employees. In most cases, software developers do not belong to this category. However, they would still need to test the software with it signed. This can be achieved using testing certificates. For details on how to create and sign using test certificates, Digital Code Signing Step-by-Step Guide has detailed step-by-step instructions and explanation on how to do this using Authenticode test certificates.

Delay Signing

If you need to sign an assembly that needs to be strong named so that you can test but have no access to your organization's private key, you can delay sign your assembly. Delay signing allows testing to be done on assemblies that need to be strong named with the actual signing performed later when development is completed. The article Delay Signing an Assembly explains delay signing issues and resolution in great detail for assemblies written in C# and Visual Basic .NET.

Briefly, in order to enable delay signing you would need to add a special assembly attribute AssemblyDelaySign(true). The wrapper assemblies also need to be strong-named and this can be done using tlbimp /delaysign /keyfile:PartialKeyPair.snk SourceTypeLibrary. The command will produce assembly for partial signing.

The next step is to disable assembly verification. This needs to be done for the COM wrappers and for the COM add-in assembly using the sn –Vr command. The sn –Vr command accepts an assembly whose verification you want to skip (it also may be wild-carded as * for all assemblies in the directory) and optional list of users that will be able to skip the verification. After the code is tested and runs correctly, the next step will be to forward the assemblies to an authority that possesses the private key for a full signature using the sn -Rc <assembly> <keypaircontainer> command. Delay Signing an Assembly explains the above in great detail.

.NET Security Consideration—Common Language Runtime Security Policy

Under the default common language runtime security policy and in most organizations' common language runtime security policy configurations, your assembly will be able to run with Full Trust. However, in rare cases, it is possible that an organization's common language runtime security policy has been configured where permissions are lowered. In such a scenario, your assembly will not be allowed to run fully trusted. In a case like this, it will be the developer's responsibility to provide the needed evidences to enable an assembly to be granted Full Trust when loaded. These evidences need to be added to the CLRLoader.cpp file.

Deploying Your Solution

When deploying your solution, it is important to keep in mind that all assemblies and the shim DLL need to be placed in the same directory on the target machine. Then the shim needs to be self-registered using regsvr32.

The simplest way to do that is by using a Visual Studio .NET setup and deployment project. This can be done by creating an empty setup and deployment project, and adding the COM add-in assembly and shim DLL project output. Then select the shim project output and change the Register property to vsdrpCOMSelfReg. Compiling the setup and deployment project will produce an .msi file that contains all the files you need to distribute to the public as a single unit.

Again, details on how to do this are discussed in the step-by-step tutorials:

Next Steps

There are many advantages in using the shim solution, two of which are:

  • You will be able to digitally sign the shim DLL and therefore comply with the Office XP security signature checking mechanism. This way, users of your managed COM add-ins can keep their Office XP security settings at its highest.
  • You will have some level of control over what the common language runtime does.

Details on how to actually incorporate the shim solution into your managed COM add-in projects are discussed in the step-by-step tutorials on MSDN:

The tutorials will also show you how to build an .msi file discussed in the Deploying Your Solution section. For more information about the .NET Framework, .NET security, .NET and COM Interop, and so forth, here is a list of references: