Partially Trusted Assembly Considerations


As I mentioned earlier in this chapter, by default .NET code deployed over the Web will be awarded a smaller set of permissions than code launched directly from the hard drive in the MyComputer zone. Any assembly, whether it's an .exe or a .dll, that's launched from the MyComputer zone will be fully trusted by default, and this means that it can do anything that the launching user is allowed to do. Assemblies deployed from the Web, on the other hand, will be partially trusted , because they aren't allowed to do everything. If you're building assemblies that are designed to work in a partially trusted environment, you need to take this into account, or else your users will see a lot of security exceptions.

For example, the wahoo and wahooControl assemblies are designed to work within the restricted set of Internet permissions, and this meant that it was difficult to implement the functionality I wanted. The challenging areas I encountered included the following:

  • Allowing partially trusted callers

  • Remembering and restoring user settings

  • Handling keystrokes

  • Communicating with Web services

  • Reading and writing files

  • Parsing command line arguments

Allowing Partially Trusted Callers

The first problem I ran into when deploying my wahoo.exe and wahooControl.dll assemblies was enabling wahoo.exe to make calls into wahooControl.dll. This happened as soon as I signed wahooControl.dll with a public/private key pair (as all .NET assemblies should be signed):

 [assembly: AssemblyKeyFileAttribute("wahoo.key")] 

.NET has a giant Frankenstein switch for each signed assembly, and this switch determines whether the assembly is secure enough to be called from partially trusted assemblies. By default, a signed assembly does not allow calls from partially trusted assemblies. This means that after wahooControl.dll is signed, by default it can't be called from a partially trusted assembly, even the one that caused it to be downloaded (in this case, wahoo.exe). To enable an assembly to be called from a partially trusted assembly, you need to set the assemblywide AllowPartiallyTrustedCallersAttribute (APTCA):

 [assembly: AllowPartiallyTrustedCallersAttribute] 

Although setting ATPCA enables wahooControl.dll to be called from wahoo.exe, it also allows it to be called from any other partially trusted assembly. You should be very careful when you apply APTCA, because now you're on the hook to make sure that your assembly is robust in the face of partially trusted callers. Microsoft itself was cautious in applying this attribute, as you can see in Table 15.4, which shows the major .NET assemblies that allow partially trusted callers as of .NET 1.1. [7]

[7] To generate this list, I used Keith Brown's most excellent FindAPTC utility, available at http://www.develop.com/kbrown/security/samples.htm

Table 15.4. Major .NET Assemblies and Their APTCA Setting

Assembly

ACPTA Set

Accessibility.dll

Yes

CustomMarshalers.dll

No

Microsoft.JScript.dll

Yes

Microsoft.VisualBasic.dll

Yes

Microsoft.VisualBasic.Vsa.dll

No

Microsoft.VisualC.Dll

No

Microsoft.Vsa.dll

Yes

Microsoft.Vsa.Vb.CodeDOMProcessor.dll

No

mscorcfg .dll

No

mscorlib.dll

Yes

RegCode.dll

No

System.Configuration.Install.dll

No

System.Data.dll

Yes

System.Data.OracleClient.dll

No

System.Design.dll

No

System.DirectoryServices.dll

No

System.dll

Yes

System.Drawing.Design.dll

No

System.Drawing.dll

Yes

System.EnterpriseServices.dll

No

System.Management.dll

No

System.Messaging.dll

No

System.Runtime.Remoting.dll

No

System.Runtime.Serialization.Formatters.Soap.dll

No

System.Security.dll

No

System.ServiceProcess.dll

No

System.Web.dll

Yes

System.Web.Mobile.dll

Yes

System.Web.RegularExpressions.dll

Yes

System.Web.Services.dll

Yes

System.Windows.Forms.dll

Yes

System.XML.dll

Yes

Any assembly that doesn't have the ATPCA setting cannot be used from any partially trusted assembly, even those given the "Everything" permission set. Only fully trusted assemblies can access assemblies that aren't trusted for partially trusted callers. In addition, you must use APTCA to mark assemblies containing WinForms controls to allow the controls to be hosted in IE.

Settings

After an application has been run once, even an NTD application, users expect that any settings that they have changed will be saved for the next time the application is run. Storing and restoring application and user settings is covered in Chapter 11: Applications and Settings, but there are special considerations when you manage settings from partially trusted code.

For example, by default partially trusted code doesn't have permission to access the Registry or permission to access the file system without user interaction. However, isolated storage is explicitly allowed from partial trust, and this makes isolated storage the perfect place for user settings. Recall from Chapter 11: Applications and Settings how isolated storage works:

 void MainForm_Closing(object sender, CancelEventArgs e) {   // Save the form's position before it closes  IsolatedStorageFile store =   IsolatedStorageFile.GetUserStoreForAssembly();   using( Stream stream =   new IsolatedStorageFileStream("MainForm.txt",   FileMode.Create,   store) )  using( StreamWriter writer = new StreamWriter(stream) ) {     FormWindowState state = this.WindowState;     this.WindowState = FormWindowState.Normal;  writer.WriteLine  (ToString(this.Location));  writer.WriteLine  (ToString(this.ClientSize));  writer.WriteLine  (ToString(state));   } } 

The ToString method is a helper for converting simple types to strings using a type converter:

 // Convert an object to a string string ToString(object obj) {  TypeConverter converter =   TypeDescriptor.GetConverter(obj.GetType());   return converter.ConvertToString(obj);  } 

Although type converters are easy to use, they're not as full featured as .NET serialization (as covered in detail in Appendix C: Serialization Basics). However, because .NET serialization can be used to set an object's private fields, its use is strictly forbidden from partial trust, making type converters a useful partial trust alternative.

Custom User Input

In addition to saving user settings between runs, most applications need to take user input of some sort . If the user input is going to one of the standard WinForms controls, that's not a problem from partially trusted code. However, if a control needs to handle special keys ”WahooControl, for example, needs to handle arrow keys ”then as of .NET 1.1, it must take special measures.

Arrow keys, Tab, and Shift+Tab are special keys because of their use in moving between controls in a form. This means that a rogue assembly allowed to consume the arrow keys could easily hijack an entire form. For that reason, a control is not allowed to override ProcessDialogKey or IsInputKey, either of which would allow such an activity. The .NET runtime will throw a security exception whenever it attempts to compile a method that contains code that creates an instance of a type that overrides these or similar methods, protecting the user from a form-jacking. Unfortunately, this means that you can't use these methods to have WahooControl handle the arrow keys.

Another way to handle the arrow keys is to let the parent form retrieve the keys in its own implementation of OnKeyDown (an action that's allowed) and pass them to the control for processing. For a form to handle keystrokes that can be handled by a child control, such as the arrow keys, a form can set its own KeyPreview property to true. All this worked fine until experimentation with .NET 1.x showed that some of the current WinForm controls, such as StatusBar and Button, don't actually let the parent form at these special keys in other controls that allow special keys through aren't on the form, like TextBox. Because the main Wahoo! form contains only a custom control and a status bar, this becomes an "issue." As a workaround, the main Wahoo! form creates an invisible TextBox and adds it to the list of controls that the form is hosting:

 public MainForm() {   ...  // HACK: Add a text box so that we can get the arrow keys   Controls.Add(new TextBox());  } 

Frankly, I'm not proud of this technique, but it lets the arrow keys through in a partially trusted environment, and one does what one must while waiting for a new platform to shake out.

Communicating via Web Services

Communicating with the user is not the only job of an NTD application. Very often an application must also communicate to the outside world. In the partially trusted zones, this communication is limited to talking back only to the originating site and only via HTTP requests and Web services. Luckily, the originating site is often what we want to talk to anyway, and Web services are easily flexible enough to handle the majority of our communication needs.

In addition to being flexible, Web services provide a much saner model for the split of server-side and client-side code. Instead of maintaining client state on the server, as a Web application often does, Web services typically provide a stateless end point that receives requests for service. These requests are typically large-grained and atomic in order to reduce requests and to let the client maintain its own state.

Generating the client-side proxy code necessary to talk to a Web service is as easy as adding a Web Reference to your project. You do this by pointing VS.NET at the URL for the Web service's WSDL (as discussed in Chapter 14: Multithreaded User Interfaces). Calling a Web service is a little tricky, however, because partially trusted code isn't allowed to make Web service calls anywhere but back to the originating server. It's up to you to make sure that the URL, which is hard-coded into the generated Web service proxy code, points at the originating server. You can do this by replacing the site in the hard-coded URL with the site that you discover dynamically using the application domain's appbase:

  // Get a client-side Web service proxy of any type, replacing   // the "localhost" site with the appbase site   static SoapHttpClientProtocol GetServiceForAppBase(Type type) {   // Create an instance of the service using .NET Reflection   SoapHttpClientProtocol service = (SoapHttpClientProtocol)   type.Assembly.CreateInstance(type.FullName);   try {   // Set URL to server where this came from   string appbase =   AppDomain.CurrentDomain.BaseDirectory;   string site =   System.Security.Policy.Site.CreateFromUrl(appbase).Name;   service.Url =   service.Url.Replace("//localhost/", "//" + site + "/");   }   // If we can't create a site from the appbase,   // then we're not an NTD app and there's no reason to   // adjust the service's URL   catch( ArgumentException ) {}   return service;   }  void GetHighScores() {   // Get scores  WahooScoresService service = (WahooScoresService)   GetServiceForAppBase(typeof(WahooScoresService));  WahooScore[] scores = service.GetScores();   // Show high scores... } 

The GetServiceForAppBase helper creates an instance of any type of client-side Web services proxy and then, if the application is NTD, replaces the "localhost" site with the site indicated by the appbase. This makes it handy not only to test your NTD application on your own machine from the MyComputer zone, but also to get the appropriate site when the NTD application is launched from an URL.

Reading and Writing Files

After I'd gotten the current high scores via the Web service, I found that I wanted to be able to cache them for later access (to savor the brief moment when I was at the top). .NET makes it easy to read and write files and to show the File Save and File Open dialogs. Unfortunately, only a limited subset of that functionality is available in partial trust. Referring again to Table 15.1, notice that the Intranet zone has unrestricted file dialog permissions but no file I/O permissions. This means that files can be read and written, but not without user interaction.

Unrestricted access to the file system is, of course, a security hole on par with buffer overflows and fake password dialogs. To avoid this problem but still allow an application to read and write files, a file can be opened only via the File Save or File Open dialog. Instead of using these dialogs to obtain a file name from the user, we use the dialogs themselves to open the file:

 SaveFileDialog dlg = new SaveFileDialog(); dlg.DefaultExt = ".txt"; dlg.Filter = "Text Files (*.txt)*.txtAll files (*.*)*.*"; // NOTE: Not allowed unless we have FileIOPermission //dlg.AddExtension = true; //dlg.FileName = "somefile.txt"; if( dlg.ShowDialog() == DialogResult.OK ) {   // NOTE: Not allowed to call dlg.FileName  using( Stream stream = dlg.OpenFile() )  using( StreamWriter writer = new StreamWriter(stream) ) {     writer.Write("...");   } } 

Notice that instead of opening a stream using the SaveFileDialog FileName property after the user has chosen a file, we call the OpenFile method directly. This gives partially trusted code the ability to read from a file, but only with user intervention and providing the code no knowledge of the file system.

Command Line Arguments

One commonly used option when launching an application that doesn't bring to mind security restrictions (but still has them, as we'll see) is passing command line parameters. In a normal application, command line parameters are available from the string array passed to Main:

  static void Main(string[] args) {  foreach( string arg in args ) {     MessageBox.Show(arg);   }   ... } 

Similarly, URLs have a well-known syntax for passing arguments:

http://itweb/hr452.exe?uid=csells&activity=vacation

The combination of the two makes it seem natural to be able to pass command line arguments to NTD applications using the special URL syntax. Unfortunately, the support for pulling command line arguments from the launching URL is new to 1.1 [8] and underdocumented. Also, because the launching URL is used to create the path to the .config file, full support for command line arguments requires that some code be run on the server side as well.

[8] There is a workaround to enable pulling command line arguments from the launching URL that works in .NET 1.0, too, and it's covered later in the chapter.

Client-Side Support for NTD Arguments

To pull the arguments out of the launching URL requires, first, that we have access to the launching URL from within the NTD application. To access the launching URL, .NET 1.1 provides the APP_LAUNCH_URL data variable from the application domain:

 // Works only for .NET 1.1+  AppDomain domain = AppDomain.CurrentDomain;   object obj = domain.GetData("APP_LAUNCH_URL");  string appLaunchUrl = (obj != null ? obj.ToString() : ""); System.Windows.Forms.MessageBox.Show(appLaunchUrl); 

The URL used to launch the NTD application, including arguments, is provided in full by APP_LAUNCH_URL. Unfortunately, APP_LAUNCH_URL isn't available in .NET 1.0. However, because the path to an NTD application's .config file is only the URL (including arguments) with ".config" tacked onto the end, we can use that to pull out the equivalent of the APP_LAUNCH_URL in .NET 1.0. For example, suppose we launch an application from this URL:

http://foo/foo.exe?foo=bar@quux

That yields the following .config file path:

http://foo/foo.exe?foo=bar@quux.config

The application domain provides access to the .config file path, so we can use that and a little string manipulation to get what we need:

 // Works only for .NET 1.1+ AppDomain domain = AppDomain.CurrentDomain; object obj = domain.GetData("APP_LAUNCH_URL"); string appLaunchUrl = (obj != null ? obj.ToString() : ""); // Fall-back for .NET 1.0 if( appLaunchUrl == "" ) {  const string ext = ".config";   string configFile = domain.SetupInformation.ConfigurationFile;   appLaunchUrl =   configFile.Substring(0, configFile.Length - ext.Length);  } System.Windows.Forms.MessageBox.Show(appLaunchUrl); 

No matter which way you get the URL used to launch the NTD application, both default Intranet and Internet permissions for .NET 1.x allow access to command line argument information from partially trusted clients . After you've got the full URL, it can be parsed for the arguments. The question mark should be used to pull off the arguments at the end, and the ampersand should be used to separate individual arguments. Unfortunately, although .NET provides classes for easily parsing and decoding query string-like URL command line arguments, partially trusted applications don't have permissions to use them, so parsing must be done by hand. [9]

[9] The samples that come with this book provide source code for full parsing and decoding of URL arguments.

Server-Side Support for NTD Arguments

The client side is not all there is to handling command line arguments for NTD applications. The URL, including arguments, is used to produce the path to the .config file, so if you want to serve up a .config file, you need some server-side code to deal with requests for .config files formed this way:

http://foo/foo.exe ?foo=bar@quux. config

These requests must be translated into requests like the following, with the arguments stripped away:

http://foo/foo.exe.config

The ASP.NET code needed to do this is beyond the scope of this book, but the included samples demonstrate one simple handler that does the job. [10]

[10] For more information: "Launching No-Touch Deployment Applications with Command Line Arguments," Chris Sells, MSDN Online, June 2, 2003

Debugging NTD Applications

As is certainly obvious by now, debugging and working around security- related issues are the hardest parts of deploying an NTD application. If you launch an NTD application via an URL, you may have noticed that there are no processes running that have that name. Instead, each application launched via an URL is hosted by ieexec.exe, which sets up the appropriate environment before loading the application. To debug an NTD application in the appropriate environment, you must debug against an instance of ieexec.exe started with the appropriate arguments to launch the application. Unfortunately, the usage of ieexec.exe is undocumented, but the reverse-engineered usage for .NET 1.1 is shown here:

  Usage: ieexec.exe <url>   urlAssembly to launch, e.g. http://localhost/foo.exe  

Launching ieexec.exe in this way will cause it to pull down the application, if it's not already cached, and launch it according to the permissions required based on the code group :

 C:\> ieexec.exe http://127.0.0.1/wahoo/wahoo.exe 

The benefit of being able to launch ieexec.exe directly like this is that it can be used as the launching application for debugging in VS.NET, as shown in a sample project's settings in Figure 15.5.

Figure 15.5. Debugging an NTD Application Using ieexec.exe

Notice that Debug Mode has been set to Program (it defaults to Project) and that Start Application has been set to the full path of ieexec.exe so that it will be used to host your application. The Command Line Arguments field has been set to the URL used to launch the application. To test default Intranet or Internet permissions when launching NTD applications hosted on the local machine, you use the "localhost" and "127.0.0.1" sites, [11] respectively. With these settings, starting an application under debugging will provide you a normal interactive debugging session, but the permissions will be reduced to match the evidence awarded as if the application were launched with an URL, just as it will be deployed. This is really the only way to catch permissions problems when you target a partially trusted environment, so I encourage you to debug in this manner before shipping your NTD application.

[11] IE looks for a "." in the site to determine the zone that an URL indicates. For example, "itweb" is in the Intranet zone because there's no ".", whereas "google.com" is in the Internet zone because it has a ".".

Under .NET 1.0, the ieexec.exe usage was considerably different. However, you can achieve the same results when executing ieexec.exe under .NET 1.0 by tacking a 0 (zero) onto the end of the command line:

 C:\> ieexec.exe http://127.0.0.1/wahoo/wahoo.exe   

Although ieexec.exe is useful for debugging, its usage is undocumented, so future versions of the .NET Framework may well launch NTD applications differently (as demonstrated by the different usages between .NET 1.0 and .NET 1.1). Hopefully, future versions of .NET will provide a much more seamless debugging experience so that we needn't concern ourselves with the ieexec.exe usage.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net