Security Considerations for ClickOnce Deployments
Dominick Baier, DevelopMentor
Keith Brown, Pluralsight
Microsoft Visual Studio .NET
Summary: In this article, Dominick Baier and Keith Brown show you how permission elevation works in ClickOnce, how you can modify the default behavior, and how to securely deploy ClickOnce applications. (11 printed pages)
The Problem with "No Touch" Deployment
ClickOnce to the Rescue
The ClickOnce Loader
Modifiying TrustManager Configuration
Microsoft .NET 2.0 introduces a new deployment technology called ClickOnce. Among other things, ClickOnce will make it much easier to deploy WinForms applications from central network locations. Part of the concept of ClickOnce is that users can decide if they want to trust and run code that doesn't originate from their local machine. While this solves a lot of the problems that caused people not to use .NET 1.1 href-EXEs, this may not be desirable for every scenario. This article shows you how permission elevation works, how you can modify the default behavior, and how to securely deploy ClickOnce applications.
Microsoft .NET 1.x includes a technology commonly called "No Touch Deployment," which allows you to start an EXE file from a file share or through the browser. In the case of the browser, the file and its dependencies were downloaded, and based on their origin (Intranet / Internet), executed in a security constrained sandbox. In reality, this approach had some problems. First, the default security policy for mobile code is too restrictive for most applications to do anything useful. Second, modifying policy requires an administrator to change settings on each client. The details of these settings were not well integrated with commonly used network management technologies like Active Directory Group Policy, which didn't help the situation much. Often you ended up with applications that included setup instructions like, "Perform these 12 easy steps first (of course you need admin privileges for that), after that you can 'No Touch' deploy this application."
But even if you have a policy deployment mechanism in place, it is not always an easy decision on what to base those policies. Applications usually consist of several components; some may even be from third parties. It is not trivial to figure out which permissions these components need, and the more bits & pieces were involved it became harder to select the right type of evidence for your code groups. If you base policy on URL evidence you have to firmly trust your DNS infrastructure and there is no way to figure out if someone replaced the binaries with malicious code on that server. Strong Names usually only apply to code written in-house and there is no key revocation infrastructure to help when keys are compromised or misplaced. In addition strong names add new secrets to the project that have to be managed securely by the development team.
Other problems of 1.1 href-Exes:
- No support for configuration files
- Non-intuitive offline support
- Sub-optimal loading performance
- No UI while launching/updating applications
ClickOnce is the new deployment technology in .NET 2.0 that is slated to solve all these problems. The major design goals of ClickOnce are:
- Make installations from network shares, URLs, and CDs seamless.
- Enable shell integration. For example, after the initial installation, applications can be launched from the Start menu (even while being offline).
- Automated updates and patching.
- Better user experience by providing a standardized UI.
But the most important new feature when it comes to security is, if an application needs more permissions than it has been granted by policy, the end user can elevate permissions without the help of an administrator.
ClickOnce applications are no longer started directly by launching the exe file. Instead a new file extension ".application" has been introduced (see example 1 for a sample .application file). This file is in XML format and describes the high level requirements of an application along with the application's version. This information is coupled with a standard W3C XML Digital Signature to make these files tamper proof and authenticable (the <signature> element).
Example 1. Sample .application file
<?xml version="1.0" encoding="utf-8"?> <asmv1:assembly> <assemblyIdentity name="ExpenseApp.application" version="220.127.116.11" publicKeyToken="8fd41ed2607f2ab2" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" /> <description asmv2:publisher="Dominick Baier" asmv2:product="MyExpenseApp" xmlns="urn:schemas-microsoft-com:asm.v1" /> <deployment install="false" mapFileExtensions="true" /> <dependency> <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="ExpenseApp_1_1_0_1\ExpenseApp.exe.manifest" size="10503"> <assemblyIdentity name="ExpenseApp.exe" version="18.104.22.168" publicKeyToken="8f..b2" language="neutral" processorArchitecture="msil" type="win32" /> <hash> <dsig:Transforms> <dsig:Transform Algorithm="..." /> </dsig:Transforms> <dsig:DigestMethod Algorithm="..." /> <dsig:DigestValue>X1...w=</dsig:DigestValue> </hash> </dependentAssembly> </dependency> <Signature>...</Signature> </asmv1:assembly>
The .application file also includes a link to the application manifest file. The manifest describes the entry point of the application, the dependencies (like dependent DLLs and data files) , as well as the permissions that are required to run. The manifest file in example 2 shows a simple application that consists of a main executable, a referenced DLL, and an application data file called "data.xml."
Example 2. Sample .manifest file
<?xml version="1.0" encoding="utf-8"?> <asmv1:assembly manifestVersion="1.0"> <asmv1:assemblyIdentity name="ExpenseApp.exe" version="22.214.171.124" publicKeyToken="8fd41ed2607f2ab2" language="neutral" processorArchitecture="msil" type="win32" /> <application /> <entryPoint> <assemblyIdentity name="ExpenseApp" version="126.96.36.199" language="neutral" processorArchitecture="msil" /> <commandLine file="ExpenseApp.exe" parameters="" /> </entryPoint> <trustInfo> <security> <applicationRequestMinimum> <PermissionSet class="System.Security.PermissionSet" version="1" ID="Custom" SameSite="site"> <IPermission class="System.Security.Permissions.EnvironmentPermission, ..." version="1" Read="USERNAME" /> <IPermission class="System.Security.Permissions.FileDialogPermission, ..." version="1" Unrestricted="true" /> <IPermission class="System.Security.Permissions.IsolatedStorageFilePermission, ..." version="1" Allowed="AssemblyIsolationByUser" UserQuota="9223372036854775807" Expiry="9223372036854775807" Permanent="True" /> <IPermission class="System.Security.Permissions.ReflectionPermission, ... " version="1" Flags="ReflectionEmit" /> <IPermission class="System.Security.Permissions.SecurityPermission, ..." version="1" Flags="Assertion, Execution, BindingRedirects" /> <IPermission class="System.Security.Permissions.UIPermission, ... " version="1" Unrestricted="true" /> <IPermission class="System.Net.DnsPermission, ... " version="1" Unrestricted="true" /> <IPermission class="System.Windows.Forms.WebBrowserPermission, ... " version="1" Unrestricted="True" /> <IPermission class="System.Drawing.Printing.PrintingPermission, ... " version="1" Level="DefaultPrinting" /> </PermissionSet> <defaultAssemblyRequest permissionSetReference="Custom" /> </applicationRequestMinimum> </security> </trustInfo> <dependency> <dependentOS> <osVersionInfo> <os majorVersion="4" minorVersion="10" buildNumber="0" servicePackMajor="0" /> </osVersionInfo> </dependentOS> </dependency> <dependency> <dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true"> <assemblyIdentity name="Microsoft.Windows.CommonLanguageRuntime" version="2.0.50215.44" /> </dependentAssembly> </dependency> <dependency> <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="BusinessLogic.dll" size="16384"> <assemblyIdentity name="BusinessLogic" version="188.8.131.52" language="neutral" processorArchitecture="msil" /> <hash> <dsig:Transforms> <dsig:Transform Algorithm="..." /> </dsig:Transforms> <dsig:DigestMethod Algorithm="..." /> <dsig:DigestValue>PU72aPMmuikoi5y3h+clRcgov9I=</dsig:DigestValue> </hash> </dependentAssembly> </dependency> <dependency> <dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="ExpenseApp.exe" size="16384"> <assemblyIdentity name="ExpenseApp" version="184.108.40.206" language="neutral" processorArchitecture="msil" /> <hash> <dsig:Transforms> <dsig:Transform Algorithm="..." /> </dsig:Transforms> <dsig:DigestMethod Algorithm="..." /> <dsig:DigestValue>m0spIP4iplyZh0tW/IrWisuaO5Y=</dsig:DigestValue> </hash> </dependentAssembly> </dependency> <file name="Data.xml" size="62" writeableType="applicationData"> <hash> <dsig:Transforms> <dsig:Transform Algorithm="..." /> </dsig:Transforms> <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <dsig:DigestValue>Viie3RrQpo/AJEFBpqbkxtmpVKE=</dsig:DigestValue> </hash> </file> <Signature>...</Signature> </asmv1:assembly>
Every ClickOnce application that you install on your computer gets its own data directory, stored in your personal Documents and Settings folder. Any file included in a ClickOnce application and marked as a "data" file is copied to this directory on application install.
The <trustInfo> element contains the CAS permissions this application needs to execute (which in this sample are the permissions for the "LocalIntranet" zone). ClickOnce applications will be sandboxed to exactly these permissions upon execution. The digital signature in the .application file also includes the hash of the manifest file and thus protects these permissions. See figure 1 for an overview how these file relate to each other.
Figure 1. Relationship between manifests and files
As you can see, the biggest conceptual change is that applications now have to describe their security and deployment needs, and the user chooses to trust that application or not. This means you no longer base trust on single components of an application, but instead on the application itself. This will make life much easier in the future.
The ".application" extension is mapped to the ClickOnce loader called "dfsvc.exe." So when you double click a .application file, or link to it on a Web page, the ClickOnce loader kicks in and loads the manifest file and hands it off to the CLR. The new hosting API in .NET 2.0 includes a method called ExecuteManifest() on the AppDomain class made especially for this purpose.
Every ClickOnce user has a personal Application Trust List (ATL). You can see this list in the user portion of the Runtime Security Policy in the .NET Framework configuration snap-in. The ATL is scoped to the Application ID, the URL, and the user. That means if you trust "ExpenseApp" from your application server, you don't trust the same application from a server called HackShack.com, which is a good thing! If the application is included in the ATL, a sandboxed AppDomain is created (with the permissions specified in the manifest), and the main entry point of the application is executed. This AppDomain is hosted in an executable called AppLaunch.exe.
If the application is not in the trust list, the registered TrustManager is invoked. The TrustManager is a class that determines if an application should be trusted or not. This also includes all UI trust interactions with the user. You can write your own TrustManager if you like, but there is a default one that can be configured through the registry (more on that later).
The default TrustManager, which lives in the System.Security.Policy.TrustManager class, first checks if the application manifest is signed by a trusted publisher or a trusted deployer. If that is the case, the application will download and run, again using AppLaunch.exe. If the publisher isn't trusted, the required permissions in the manifest are evaluated. Zone Evidence is used to compare the default permissions granted by CAS policy with the specified permissions in the manifest. If the application permissions don't exceed policy permissions, the application downloads and runs without asking the user any trust questions.
If the application needs more permissions than what's granted by policy, the user is asked if he wants to trust that application and elevate permissions (figure 2). If the user clicks Run, the application is put into the Application Trust List and is downloaded and started.
Figure 2. Permission Elevation Dialog
Notice that the application is only downloaded if necessary and after all trust decisions. You can see a diagram of these decisions at load time in figure 3.
Figure 3. ClickOnce Loader
Also notice that the trust decision is now up to the user, although by default, a user can't elevate permissions for untrusted code coming from the Internet zone. We'll now define what a trusted application is and how to modify the default TrustManager behavior.
In corporate scenarios you likely don't want users to make trust decisions on their own. If TrustManager determines somehow that the application is trusted, a user will never see the permission elevation dialog, even on the first install of the application.
In ClickOnce (with the default TrustManager) there are two types of trusted applications: ones coming from a trusted publisher and ones coming from a trusted deployer.
Remember that the .application and .manifest files contain a digital signature. You can sign the manifests using an Authenticode certificate (that is simply a certificate that includes an intended purpose of 'Code Signing'). If the client trusts that certificate, TrustManager evaluates the application as trusted and directly executes it without prompting the user for permission elevation.
To get a code signing certificate you have three options:
- Generate a self signed certificate using 'makecert.exe' or Visual Studio.
- Buy one (from VeriSign, for example).
- Generate one using a Windows Certificate Server.
Option 1 is the easiest way to experiment with the technology but is really only useful for internal testing or demos. Option 2 is the typical route for an independent software vendor (ISV) who wants to make code available to other organizations or home users.
Option 3 is a good choice for code that will stay within your own enterprise. Here are the steps you can follow to get it working.
Windows 2003 Enterprise certificate authorities (CA) come with a set of Certificate Templates (managed by the certtmpl.msc snap-in). These templates configure the settings of issuable certificates. There is already a pre-installed template for "Code Signing." This template has a validity period of 1 year. If you want to modify the template (only supported on Windows 2003 Enterprise Edition), call "Duplicate Template," give it a new name, and change the settings of the template. Either way, you have to modify the security settings. Give the user(s) who should be allowed to request such a certificate "Enroll" access.
After that go to the console of your CA and navigate to the "Certificate Templates" folder. Click New->Certificate Templates to Issue, and select your code signing template. Now you can request this type of certificate from the CA web interface or the client side "Certificates" MMC snap-in.
After you request the certificate, sign your ClickOnce manifest using that certificate (see the Tool Support section for more info). The root CA and the code signing certificate must be trusted on the client. This means you have to import those certificates in the client's certificate store in the "Trusted Root Certification Authority" and "Trusted Publishers" folder accordingly.
Needless to say that the private key for this certificate is a new and very important secret in your company you have to secure somehow.
In bigger networks you don't want to install those certificates manually on every client workstation. If you are running in an Active Directory domain, there is an easy solution. You can use AD Group Policy Objects (GPO) to centrally distribute certificates.
For the root CA cert add a GPO to AD and link at the appropriate level (usually domain level). Go to Computer Settings -> Windows Settings -> Security -> Public Key Policies. Add the root CA cert under "Trusted Root Certification Authorities."
Distribution of trusted publisher can also be accomplished in a similar fashion. Add a GPO to AD and link at the appropriate level (usually for the organizational unit that should trust the application). Go to User Settings -> Windows Settings -> Internet Explorer Maintenance -> Security -> Authenticode Settings. Click Import and then Modify. If you don't want your users to modify their trusted publisher cert store on their own, you should also enable the "Lock down Trusted Publishers" feature.
After the normal GPO replication to clients takes place, every client trusts your CA and the trusted publisher certificate. They should never be bothered by trust questions for applications signed with corporate certificates.
The .NET 2.0 CLR also maintains a list of trusted deployers. Every manifest that is signed by a key that can be resolved to a trusted deployer is also trusted implicitly. This can enable scenarios in which the development department of a company hands software to an admin who approves and signs the manifest and makes the application available for deployment.
There are two ways to extend the trusted deployer list. First, there is an API to programmatically add the deployer key to the list. You could use this approach to distribute an initial MSI file to add your IT department to the trusted deployer list; afterwards you never have to touch the clients again. Another approach would be to modify the .NET Framework installation package to include the trusted deployer key. This is better suited for large scale .NET rollouts.
So far you've seen how to suppress the trust UI for designated applications. The next step for securing ClickOnce in an enterprise will likely be to disallow the execution of untrusted applications, meaning applications that don't originate from a trusted publisher or deployer.
TrustManager decides whether to silently run an application or ask users whether they trust the application. The behavior of TrustManager can be configured using the registry. The reason this configuration is in the registry, as opposed to XML files like the "classic" EnterpriseTrust.config file, is a practical one. It makes the configuration easily accessible by GPOs to configure TrustManager centrally for corporate networks.
The registry key "HKLM\Software\Microsoft\.NETFramework\Security\TrustManager\PromptingLevel" controls if ClickOnce will allow only trusted applications or if permission elevation is enabled (you have to create the registry key if it is not present). Inside of this key you can add strings for each zone, like "LocalIntranet," "Internet," and so on. The allowed values are Enabled (permission elevation enabled), Disabled (permission elevation is disabled, and the application won't run if elevated permissions are needed), and AuthenticodeRequired (only trusted applications are allowed to elevate permissions, and the user is not asked trust questions). In the following table you can see the default values for each zone:
By modifiying these values you can lock down ClickOnce so that only trusted applications are allowed to elevate permissions, even from the intranet zone. To distribute these settings using a GPO in your domain, you have to create an administrative template that can plug in the Group Policy Editor. See example 3 for a template file.
Example 3. TrustManager administrative Template
CLASS MACHINE CATEGORY ClickOnce POLICY "MyComputer" KEYNAME "Software\Microsoft\.NETFramework\Security\TrustManager\PromptingLevel" PART "Choose Prompting Level" DROPDOWNLIST REQUIRED VALUENAME MyComputer ITEMLIST NAME "Enabled" VALUE "Enabled" DEFAULT NAME "Authenticode Required" VALUE "AuthenticodeRequired" NAME "Disabled" VALUE "Disabled" END ITEMLIST END PART END POLICY POLICY "LocalIntranet" KEYNAME "Software\Microsoft\.NETFramework\Security\TrustManager\PromptingLevel" PART "Choose Prompting Level" DROPDOWNLIST REQUIRED VALUENAME LocalIntranet ITEMLIST NAME "Enabled" VALUE "Enabled" DEFAULT NAME "Authenticode Required" VALUE "AuthenticodeRequired" NAME "Disabled" VALUE "Disabled" END ITEMLIST END PART END POLICY POLICY "Internet" KEYNAME "Software\Microsoft\.NETFramework\Security\TrustManager\PromptingLevel" PART "Choose Prompting Level" DROPDOWNLIST REQUIRED VALUENAME Internet ITEMLIST NAME "Enabled" VALUE "Enabled" NAME "Authenticode Required" VALUE "AuthenticodeRequired" DEFAULT NAME "Disabled" VALUE "Disabled" END ITEMLIST END PART END POLICY POLICY "TrustedSites" KEYNAME "Software\Microsoft\.NETFramework\Security\TrustManager\PromptingLevel" PART "Choose Prompting Level" DROPDOWNLIST REQUIRED VALUENAME TrustedSites ITEMLIST NAME "Enabled" VALUE "Enabled" DEFAULT NAME "Authenticode Required" VALUE "AuthenticodeRequired" NAME "Disabled" VALUE "Disabled" END ITEMLIST END PART END POLICY POLICY "UntrustedSites" KEYNAME "Software\Microsoft\.NETFramework\Security\TrustManager\PromptingLevel" PART "Choose Prompting Level" DROPDOWNLIST REQUIRED VALUENAME UntrustedSites ITEMLIST NAME "Enabled" VALUE "Enabled" NAME "Authenticode Required" VALUE "AuthenticodeRequired" NAME "Disabled" VALUE "Disabled" DEFAULT END ITEMLIST END PART END POLICY END CATEGORY
Paste the contents to a text file with a .ADM extension (TrustManager.adm, for example), and copy the file to \Windows\Inf to the computer on which you want to add the GPO. In the Group Policy Editor go to Computer Settings -> Administrative Templates. Right click and select Add/Remove Administrative Template. Now choose the TrustManager.adm file. To see the template, go to View -> Filtering and uncheck "Only show policy settings that can be fully managed (figure 4). Now you can distribute the TrustManager settings in your corporate network.
Figure 4. ClickOnce Setting in the Group Policy Editor
The .application and .manifest files are pretty complicated beasts! Of course, you don't have to write all that XML by hand. Visual Studio 2005 includes nice support to assist you in writing partially trusted apps and deploying them using ClickOnce afterwards.
The first step to enable ClickOnce for your Windows Forms app is to go to the project properties in Visual Studio and enable the project for ClickOnce and partial trust. After that you choose the intended zone your application will run from—Intranet or Internet. You can also adjust the permissions needed to run the application on a manual basis. For example, you might start with the Intranet permissions and then add a FileIOPermission. This influences the permissions that Visual Studio will package into the manifest file. A very cool new feature is that you can let Visual Studio figure out which permissions are needed by analyzing your assembly. Just click the Calculate Permissions button (figure 5).
Figure 5. Visual Studio ClickOnce Settings (Click on the image for a larger picture)
On the "Signing" tab you can choose the code signing certificate that should be used to sign the manifest. You can select from the local certificate store or browse to a .PFX file. If you don't choose one, Visual Studio will generate a self-signed certificate (figure 6).
Figure 6. Signing a ClickOnce Manifest with Visual Studio (Click on the image for a larger picture)
Finally, the "Publish" tab allows you to upload all binaries and manifest files to the location of your choice. If you choose a Web location, Visual Studio will automatically generate a skeleton Web page to deploy the application called "publish.htm."
If you don't have Visual Studio 2005 installed (in a staging environment, for example), or if you want to automate the process, the .NET Framework SDK ships with a tool called "Mage.exe," which provides the same functionality.
Centrally deploying Windows Forms applications is a requirement in modern enterprises. The .NET 1.1 href-EXE approach didn't work very well in most scenarios. The default security sandbox is too restrictive for most applications and policy deployment can get very complex.
ClickOnce solves this problem by basing trust on applications as opposed to individual assemblies and allowing the end user to elevate permissions. For scenarios in which you don't want your users to make their own trust decisions you can customize the behavior by signing your application manifests and configuring TrustManager. All these settings can now be centrally managed using Active Directory Group Policies. These features in combination will make the vision of "No Touch Deployment" more practical in the future.
About the authors
Dominick Baier leads the security curriculum at DevelopMentor. This includes teaching and authoring courses about .NET, ASP.NET, WinFX, and 'Vista' security. He holds a degree in Computer Science, is a certified BS7799/ISO17799 Lead Auditor, and speaks at various conferences about application security. When not teaching he spends his time researching security, doing audits and penetration tests, and helping other developers around the world to build more secure applications. Dominick is a Microsoft MVP in the "Visual Developer - Security" category.
You can find a lot of security related resources as well as conference slide decks and tools/sample code at Dominick's blog under www.leastprivilege.com.
Keith Brown is a co-founder of Pluralsight, a premier developer training company, where he focuses on security for developers. Besides writing the Security Briefs column for MSDN Magazine, he authored The .NET Developer's Guide to Windows Security (Addison Wesley, 2004) and Programming Windows Security (Addison Wesley, 2000). Keith also speaks at many conferences, including TechEd and WinDev. Check out his blog at www.pluralsight.com/keith.