Code Signing and Strong Names

This is second document from way back in 2002 talks about code signing and strong names. Visual Studio has much better support for key generation these days, but the discussion of delay signing is kind of interesting. This one is redacted because it was part of a memo I wrote to a client, but I think it still has relevance in 2008 although you may not be as well versed in COM as the people I wrote this for over 6 years ago.

Edit: 8/12/2008 – How embarrassing, I included some content from MSDN when I put this memo together so long ago. You can read the central portion about the nature of strong names here. I have no idea what other content in this post is unattributed, but if you see something in this post that you think came from elsewhere, it probably did. Please let me know and I’ll give credit where credit is due. I thought about just deleting the post, but it’s an important subject and I think most of the content at the top and the bottom is original. That said, many brain-cells have died in the intervening years. (Truth be told, I’d forgotten about this memo, but found it using desktop search while looking for the one on Dispose.)

–Doug Ware

The list of differences between COM and .NET development is long. .NET is a new generation of technology, it is not an evolution of COM, and it was designed to resolve many of the frustrating aspects of COM development. One of these frustrations is the situation known colloquially as "DLL Hell".

COM Components are Global to the Machine and have Identity…

COM components are, generally speaking, always global to the machine on which they are installed. When a new program is installed, the installer registers each component in the Windows registry. A typical entry contains the component’s Program ID, for example WinWord for Microsoft Word, and a hexadecimal Globally Unique Identifier, also known as a GUID.

Any application installed on the same machine that knows either the Program ID or the GUID can execute code inside the component. This feature makes it easy for developers to take advantage of common components for user interfaces, database access, and many, many other features.

DLL Hell occurs when a version of a given COM component that is incompatible with a previously installed version is registered, usually by installing a new program, which causes existing applications to malfunction. Over time, this scenario has cost countless dollars and lost hours of productivity.

However, it is also advantageous for COM components to have identity based on a GUID because it becomes possible to establish security policies based on specific component. Role based security is based on the intersection of NT Users or Groups and GUID’s.

By Default, .NET Components are Local to a Project and have no Identity…

Most components do not contain generic capabilities that people want to reuse. Furthermore, the process of registering components in the registry makes installing programs more difficult. .NET supports global registration of components when necessary, but by default, when a program needs to load a given component, it looks for that component in the local path.

The default behavior allows many .NET applications to be deployed by copying the files to the target machine. This is referred to as XCOPY installation in the .NET documentation. The default behavior also allows several applications, or multiple versions of the same application, with different versions of the same component to coexist on the same machine without DLL Hell style conflicts. This is referred to as side-by-side installation in the .NET documentation.

However, because .NET components, properly known as assemblies, do not have an analog to a GUID by default, additional work must be done to provide an identity if they are to be installed globally or if a specific security policy is desired.

.NET Assemblies Get Identity from Strong Names

COM components get their identity from a GUID. .NET assemblies get identity via strong names.

A strong name provides the assembly’s identity — its simple text name, version number, and culture information (if provided) — plus a public key and a digital signature. The strong name is generated from an assembly using the corresponding private key. Visual Studio .NET and other development tools provided in the .NET Framework SDK can assign strong names to an assembly. Assemblies with the same strong name are expected to be identical.

You can ensure that a name is globally unique by signing an assembly with a strong name. In particular, strong names satisfy the following requirements:

  1. Strong names guarantee name uniqueness by relying on unique key pairs. No one can generate the same assembly name that you can, because an assembly generated with one private key has a different name than an assembly generated with another private key.
  2. Strong names protect the version lineage of an assembly. A strong name can ensure that no one can produce a subsequent version of your assembly. Users can be sure that a version of the assembly they are loading comes from the same publisher that created the version the application was built with.
  3. Strong names provide a strong integrity check. Passing the .NET Framework security checks guarantees that the contents of the assembly have not been changed since it was built. This provides a level of protection against certain types of Trojans that work by injecting malicious code into common components, an assembly with a strong name can not be altered without invalidating the internal hash code. If the code is not valid, the runtime will refuse to execute the assembly

Note, however, that strong names in and of themselves do not imply a level of trust, such as that provided by a digital signature and supporting certificate.

By default, two assemblies installed on the same machine have the same level of permissions even if one is strongly named and the other is not.

Once an assembly is provided with a strong name, it becomes possible to register the assembly globally to the machine and to set security policy on it. Note however that, unlike a COM GUID, an assembly’s strong name includes its version number, thus enabling side-by-side execution of different versions of the same assembly.

Do not confuse the signature of a strong name with Authenticode Signatures. Although Authenticode is supported in .NET it is a completely separate topic. The primary differences are:

  • Strong names are lighter weight. The implementation is simpler, the process involved is less complex, and there is no network connection made to a third party for verification.
  • There is no automatic way to associate a strong name with a specific publisher.
  • No means exist to revoke the use of a given strong public key if it is compromised. If such a situation occurs, you must revoke the permission of compromised assemblies, resign, and redeploy them.
  • Strong names never result in a dialog asking the user to make trust decisions. Specific security policies for a given public key must be deployed prior to runtime if needed.

.NET Security

.NET provides a security model that can be configured at the enterprise, machine, and user level. This model provides a finer grain of control than anything previously available on the Windows platform. All .NET applications are executed within the context of Common Language Runtime, CLR. The CLR is conceptually similar to a Java Virtual Machine.

A primary virtue of this architecture is that code executes within a ‘sandbox’ and because of this cannot access any resources unless the CLR allows it to do so. This is very different from previous generations of Windows’ development technologies where executables communicated directly with the operating system.

Permissions are evaluated based on the most-restrictive configuration. In other words, if the machine’s security policy explicitly disallows an action that the user’s security explicitly allows, permission will always be denied for that action.

Furthermore, the CLR is still governed by the underlying set of permissions provided by the local machine and the domain. It is not possible to write an application that will allow the executing user the ability to access resources for which the user does not have permission.

Security Zones

Out of the box, the .NET framework security policies are based exclusively on the "zone" the code comes from. The zones are: Local Machine, Intranet, Internet, Trusted Sites, and Untrusted Sites.

Local Machine Zone

Code originating from the Local Machine zone is assumed to be safe by default and is governed by the user’s normal security.

Intranet Zone

The local intranet zone is used for content located on a company’s intranet. Because the servers and information would be within a company’s firewall, a user or company could assign a higher trust level to the content on the intranet.

Internet Zone

The Internet zone is used for the Web sites on the Internet that do not belong to another zone. The default settings allow code downloaded from these sites only minimal access to resources on the user’s computer. Web sites that are not mapped into other zones automatically fall into this zone.

Trusted Sites Zone

The Trusted sites zone is used for content located on Web sites that are considered more reputable or trustworthy than other sites on the Internet. Users can use this zone to assign a higher level of trust to specific Internet sites. The URLs of these trusted Web sites need to be mapped into this zone by the user. By default, sites in the Trusted sites zone receive no higher trust than those in the Internet zone. A user or company needs to change the level of trust granted to this zone if they want the sites it contains to be given a higher level of trust.

Untrusted Sites Zone

The Restricted sites zone is used for Web sites that contain content that could cause, or could have previously caused, problems when downloaded. This zone could be used to prevent code downloaded from these sites from running on the user’s computer. The URLs of these untrusted Web sites need to be mapped into this zone by the user.

It is possible to adjust the permissions of individual zones. The first release of the .NET framework provided a similar level of trust to the Local Machine and Intranet zones. However, service pack 1 (of .NET 1.0) lowered the trust for the Intranet Zone to disallow certain permissions. Specifically it disallowed COM interop, remoting, and non-isolated file I/O.

Organizational Options for Creating Strong Names

In order to create a strong name, you must have a key pair. The easiest way to generate a key pair is to use the strong name tool included with the .NET framework, Sn.exe. (Note: remember that I wrote this in 2002, this part is easy in VS 2005 and later!)

Key generation and signing can be done by individual development teams, resulting in a different public key for each application, or by a central authority within an organization.

Security policy can applied at install time by the installation process, or centrally administered with tools like SMS.

If the key is easily available to all members of a development team, it is increasingly difficult to ensure that the key is not compromised or even to detect a compromise should it occur.

.NET supports a process known as Delay Signing where the development team is provided with the public portion of the key pair. The developers add the public key to the assembly to establish its identity for testing purposes, but the builds simply reserve space for the digital signature.

Because the developers know the public key and space is reserved for the digital signature, it is (relatively) easy for the development team to perform its function and for a different organizational unit to complete the process of signing the code. Because the application is already built, the second organizational unit does not have to recreate the build environment in order to complete the process. It also minimizes the differences between development and release versions of the assemblies, boosting confidence that development tests on the development version was not invalidated by the signing process.

The Price of a Compromised Key

This is an interesting question. Ultimately I believe that centralizing the process of code signing is clearly the best practice. However, when considering these points, remember that:

  • Code executing in the Local Machine zone has Full Trust by default (just like any COM application).
  • .NET never gives a user more authority than Windows itself.
  • The system in question is not a candidate for distribution to a third-party.

Internally to a company, the only exploits I can see based on a compromised key require social engineering to place a malicious application with the compromised key on the Intranet or Internet and convince someone to launch it via a hyperlink (possibly in an email). Not to downplay this possibility, a crafty villain engaging in such an attack could get the same results by getting a user to run the code locally as a script, a native .exe, a COM based .exe, or a .NET .exe. A native or COM .exe would be the easiest way to actually get root on the machine. The .NET version would require breaking two levels of security, the OS and the CLR.

I provide this speculation to help to begin weighing the (hypothetical) risks versus the (unknown) costs of centralizing the code signing.

I hope this makes sense and clarifies the issues.

Author: Doug Ware