ClickOnce Security
- ClickOnce Security Overview
- Internet Explorer Security Settings Affecting ClickOnce
- Configuring ClickOnce Security Permissions
- Understanding and Managing Publisher Certificates
- Signing Application Updates
- User Prompting
- Trusted Applications' User Security Policies
- Trusted Publishers' Permission Elevation
- Adding Restricted Code Sections
- Securing the Application Based on User Roles
- Securing Access to ClickOnce Application Files on the Server
- Where Are We?
WHEN PLANNING FOR DEPLOYMENT, you need to consider a number of different aspects with respect to security. You need to consider
- How to protect the client machine from being compromised by your application's installation or execution
- How to protect the application files from being tampered with on the deployment server
- How to implement authentication and authorization based on the user's identity
- What you want to allow the application to do based on the identity of the application publisher
ClickOnce, the .NET Framework, and the Windows operating system provide facilities to address all of these considerations. This chapter will discuss these different aspects and give you a solid understanding of what protections ClickOnce provides, and how you can customize those protections to suit the needs of your particular application.
ClickOnce Security Overview
ClickOnce is designed to be a trustworthy deployment mechanism for smart client applications. This means that ClickOnce is designed to protect the client machine from being harmed by applications that it deploys. ClickOnce provides protection for the client machine at install time and at runtime, ensures that the client machine and users can identify who the publisher of the application is, and protects the application's files to ensure than no one can tamper with them after the publisher has published the application.
ClickOnce runtime protection is based on the application's identity, not on the user. ClickOnce is specifically designed to enable low-privilege users to deploy and launch smart client applications without administrator intervention. The user identity is not used directly by ClickOnce in any way. However, that does not mean that your ClickOnce application will be unprotected with respect to user privileges either. You can take advantage of .NET role-based security to prevent users from using functionality in your application if they do not have sufficient rights. Additionally, the client machine's operating system will still enforce access controls based on the logged-in user, such as limiting access to files, folders, or the registry if the user is not part of the access control list for those resources.
ClickOnce Deployment-Time Protections
ClickOnce security protection comes into play as soon as an application or update is deployed to the client machine. When files are deployed to the client machine through ClickOnce, they are isolated per user, per application, and per version under the user's profile. The application deployment itself is nothing more than a series of files copied into an isolated folder under the user's profile. If you have worked with .NET isolated storage before, the ClickOnce cache folders are similar in concept, but located in a different place under the user's profile. You cannot execute any custom installation steps that make modifications to the local machine as part of the ClickOnce deployment itself (see Chapters 7 and 8 for more information on custom installation requirements). As a result of this design, there is no way that the act of deploying an application to a client machine through ClickOnce can harm other applications or data on the machine.
ClickOnce Runtime Protections
ClickOnce and the .NET runtime provide runtime protections for the client as well. ClickOnce relies on the Code Access Security (CAS) infrastructure of the .NET Framework for enforcing those runtime protections, but ClickOnce security is configured and managed a little differently than for non-ClickOnce deployed applications. For a quick overview of CAS, see the sidebar entitled A Short Primer on Code Access Security.
ClickOnce security is applied at the application level, instead of at the individual assembly level as it is in a normal .NET application. Your entire ClickOnce application (the application executable and all assemblies that it loads) are treated as a single unit for the purposes of deployment, versioning, and security. When an application is deployed through ClickOnce, the application manifest specifies what security permissions the application needs to run. These permissions are based on CAS.
As the application is launched by ClickOnce, the runtime first evaluates what URL or UNC path was used to deploy the application to the client machine (the path to the deployment manifest on the deployment server). This path is treated as the launch path. Based on this path, the runtime associates your application with one of the built-in location-based code groups (My Computer, LocalIntranet, Internet, Trusted Sites, or Restricted Sites zones). The runtime determines what set of permissions should be granted to your application based on the zone that it was launched from and compares that to the set of permissions requested by the application.
If the requested permissions in the application manifest are less than or equal to the set that would be granted based on the launch zone, then no elevation of permissions needs to occur and the application can simply launch and run. If the application attempts to perform an operation that exceeds the granted permissions, then a SecurityException will be thrown.
To see this in action, do the following.
- Create a new Windows Application project in Visual Studio, and name the project RuntimeProtectionApp.
- From the toolbox, add a button to the form.
- Double-click on the button to add a Click event handler for the button.
- Add the following code to the event handler:
private void button1_Click(object sender, EventArgs e) { StreamWriter writer = new StreamWriter('AttemptedHack.evil'); writer.WriteLine('If I can do this, what else could I do??'); writer.Close(); }
- Add a using statement for the System.IO namespace to the top of the file:
using System.IO;
- Open the project properties editor (choose Project > RuntimeProtectionApp Properties).
- On the Security tab, check the checkbox labeled Enable ClickOnce Security Settings, and click the radio button labeled This is a partial trust application (see Figure 6.1).
Figure 6.1 ClickOnce Security Settings
- Publish the application by choosing Build > Publish RuntimeProtectionApp.
- When the Publish wizard appears, click the Next button.
- In the second step of the Publish wizard, select the option to make the application available online only (see Figure 6.2) and then click Finish.
Figure 6.2 Selecting Install Mode in the Publish Wizard
- Click the Run button in the publish.htm test page when it appears in the browser. This launches the application.
- Press the button that you added to the form in step 2, causing the application to try to write a text file to the current working directory (which in this case is the C:\Windows\Microsoft.NET\Framework\v2.0.50727 folder, since the application is marked for partial trust as discussed in Chapter 5).
- A SecurityException will be thrown for the FileIOPermission type, because the default LocalIntranet zone security permissions do not include that permission. The permission is demanded by the StreamWriter class when you construct an instance of the StreamWriter. Since the application does not catch the exception, the dialog shown in Figure 6.3 will display.
Figure 6.3 Unhandled Exception Dialog
- Click the Quit button to exit the application.
In this example, the application requested permissions that did not exceed the permissions granted by the launch zone. This is because you selected partial trust and the default zone for partial trust is the Local Intranet zone. When you installed the application by clicking on the Run button in the publish.htm test page, the address used was http://<your-machine-name>/RuntimeProtectionApp/RuntimeProtectionApp.application. The runtime evaluates this address to the Local Intranet zone (based on the server address portion of the URL: http://<your-machine-name>/) and compares the requested permissions in the application manifest to the permissions for that zone. Since they match, no additional prompting is needed based on security and the application launches.
However, just because the application only requests a certain set of permissions based on its manifest does not mean that there is not code in that application that might try to do some operation that exceeds the granted set of permissions. In this example, the application contains code that tries to perform a file write to the local directory. That operation triggers a check for FileIOPermission for the file that is being written. Since the Local Intranet zone does not include that permission, a SecurityException is thrown at that point.
These protections are designed to ensure that your application does not inadvertently do something on the user's machine that it was not designed to do. This could result from bugs in your code, debug code that was left behind unintentionally, or it could happen if your application manages to load some other assembly that does something more than you expect it to. For example, suppose you design a smart client application that acts as a data entry client for a distributed application. Based on your design, that application should only present a rich interactive user interface for the user to view, enter, and manipulate data that gets passed to your middle-tier application server through Web services.
Suppose you choose to use some third-party UI component to speed your development. Unknown to you, the code inside that component collects any values that are entered through its controls and transfers that data to some unknown location via a Web request for intelligence gathering. If you deployed this application with full trust, the component would be able to do just that and you may never even know it is happening behind the scenes. However, if you deployed your application with partial trust and restricted the WebPermission options to only allow calls to your middle-tier servers, then a security exception would be thrown when that nefarious component tried to do its evil deeds. By restricting the permission set, you would be protecting the user from that hidden data transfer.
Using a restricted set of permissions through partial trust is an excellent way to prevent your application from doing anything it was not designed to do. Unfortunately, for a lot of meaningful things that you might want to do in your application, such as doing on-demand updates through ClickOnce or making remote calls through Windows Communication Foundation, you will be required to set your application for full trust due to the more advanced things the Framework does for you under the covers to provide those capabilities. You can still lock down permissions for specific sections of your code, however (see the section Adding Restricted Code Sections later in this chapter for an example of how to do that).
If the application manifest requests permissions that exceed the launch zone permissions, such as full trust, then those permissions need to be granted to the application somehow so it can launch. This can be done either through user prompting (the default) or automatically based on trusted publishers. Both of these approaches are covered later in this chapter.
ClickOnce Size Limitations for Online-Only Applications
A partial trust online-only application can run without any user prompting, depending on the permissions the application requires and the zone it is running from. To prevent such an application from filling up the hard disk by downloading many large files, ClickOnce restricts the total size of a partial-trust online-only application to be half the online cache quota on the machine. This size is checked at download time as bits are being downloaded, and the ClickOnce launch will fail once the limit is exceeded. The default cache quota is 250MB, so partial-trust applications larger than 125MB should ask for full trust.
ClickOnce Tamper Protections
ClickOnce protects the files that your application is composed of by using digital signatures. When you publish an application with ClickOnce, you have to sign the deployment and application manifest with an Authenticode Class 3 Code Signing publisher certificate. Authenticode certificates are based on public-private key cryptography. Publisher certificates contain both a public and a private key. The public and private keys have a mathematical relationship that makes it so anything you encrypt with one of the keys, you can decrypt with the other. However, the complexity of the mathematical relationship is such that it is extremely difficult to come up with one key when you just have the other. With the strength of current cryptographic keys, it would take hundreds or thousands of years of heavy-duty computing to figure out the value of one key if you just know the value of the other.
As the names imply, the intent is that you keep one key (the private key) to yourself, but you can freely hand out the public key to anyone who wants it. Once others have your public key, you can encrypt a message or file with your private key and give the message or file to them, and they can decrypt it using the public key with a strong assurance that the message or file they decrypted actually came from you (or at least someone who has access to your private key). Likewise, they can encrypt a message or file with your public key and give it to you, and they can be sure that only you can decrypt that message or file and see the contents.
When you sign a file with a certificate, the signing mechanism computes a hash of the file's contents using cryptographic methods. In computing the hash, it disregards a reserved section of the file into which it will insert the digital signature once is has been computed. Once the hash has been computed, the hash is encrypted with the private key of the publisher certificate. The encrypted version of the hash is the digital signature. This signature and the public key from the certificate used to encrypt the hash are inserted into the reserved location in the file. Now anyone who receives that file can compute the file's current hash using the same algorithm that was used to generate the original hash. They can then extract the digital signature and decrypt it using the public key embedded in the file with the signature. After they have decrypted the signature, they have the original hash that was computed by the publisher. If they compare the original hash and the hash they just computed, they can confirm that no one has tampered with the file since it was signed by the publisher, because any modifications to any part of the file will modify the computed hash and it will be different from the original hash.
This approach is used by ClickOnce to digitally sign your deployment and application manifests when you publish your application. It is also used by .NET for strong naming assemblies. Strong naming is just a similar digital signature approach. In the case of ClickOnce, the digital signature is embedded in the manifests as XML. In the case of strong naming, the digital signature is computed when an assembly is compiled, and is embedded in the assembly manifest in binary form.
In addition to digital signatures providing a guarantee that the manifests have not been tampered with since you published your application, they also provide tamper protection for all of your application files. When your application manifest is generated, a hash of each of the files in the application is put into the application manifest along with the rest of the file information. When ClickOnce deploys or updates your application, it computes the hash of each file as it is downloaded from the server and compares the hash to the one embedded in the downloaded application manifest. Since the application manifest is signed and can't be tampered with to change the hash values for application files, there is no way for someone to tamper with any of your application files, because ClickOnce will refuse to launch your application if the application file hashes don't match after they have been downloaded.