Using MSI or a Strong name to store .NET apps on a network server (Part 2)

Using MSI or a Strong name to store .NET apps on a network server (Part 2).

Introduction

Here is part 2 of the series on putting .NET applications on the network and getting around the security issues. This time I will be describing how to use a strong name key pair to mark an assembly as a strong named assembly, then we will do something similar to the last article in marking that strong name key as fully trusted. If you did not get a chance to read part one, you might want to take a few minutes to read it.

Background

So there are options in .NET for distributing applications. If you have a local intranet you can host an ASP.NET application. This can be somewhat limiting for extremely complex applications that are better suited for a Windows style application. It is not always easy to release something new on the web either, even if it is your local intranet. You can use a web server to help distribute the application. Something like: it checks to see what version you have locally and if there is a newer version it is installed locally and then launched. You can use a terminal server. The application only exists on the terminal server and your users have to log into the terminal server to access the app. We have tried all these approaches and personally I don't care much for them. So on to the next two. First, the one I like best, create a machine level runtime security policy. Then put it into a MSI script and have your users run the MSI script (they only have to once), or have Active Directory distribute the MSI script at login. This policy marks a network server and folder on that server as fully trusted. The other option is to use the SN.exe tool to create a strong name key and mark all of your applications with the strong name key and then create a runtime security policy allowing anything with that strong name key to be fully trusted. In part 2 of this article, I will be reviewing how to do the machine level runtime security policy that marks a strong name key as fully trusted.

One more thing to know here on strong name keys. When I first started working with them, I assumed that you have to create a new strong name key pair for each application. Well, of course, that is silly. You should in fact only have one strong name key pair and reuse it for any assemblies you want to mark as strong name assemblies. Of course there are good reasons to use different strong name keys, but that is a subject for a different discussion.

How do you do it...

Let's start off again with what you get when a .NET app is not fully trusted.

Next we need to create the strong name key pair. You use the SN.exe tool for this. If you have Visual Studio installed then you can open a Visual Studio .NET command prompt. This allows the command prompt to know what the SN.exe is, otherwise you will need to navigate to where the sn.exe program resides which is in the .NET framework folder. Navigate to the folder you want the strong name key to be created in. The command is sn -k filename.snk:

Next you need to consume the strong name key pair in your .NET app. Open your AssemblyInfo.vb or AssemblyInfo.cs file and add this code:

'vb.net
<ASSEMBLY: AssemblyKeyFile("c:\keys\Liberty.snk")>

//or C#
[assembly: AssemblyKeyFile("c:\keys\Liberty.snk")]

Note you will need to point to your file path and key file name. You can also use the AssemblyKeyName attribute. Now you will need to compile your .NET app. This is important since the easiest way to get the strong name public key is to import an assembly that already has it compiled in.

Creating the code group

Now we will follow the familiar process from part 1 of creating a new code group. On to creating the run time security policy that marks a strong name key pair as fully trusted. You set up a machine level runtime security policy in the .NET framework configuration tool, which can be found in Control Panel->Administration tools. Note that if you are not an Administrator on your PC, you will not be able to set a runtime security policy. Under Runtime Security Policy\Machine\Code groups\All code\localIntranet_zone\, you want to add a child code group.

Put in a name and comment for the new code group:

Now you will need to select a strong name from the drop down. Then click the Import button and select the .NET app you just compiled with the strong name key. This will bring in the strong name public key into the code group dialog.

Next you need to select what security you want this folder to have. Full trust is what I usually pick, but there are several options. Then click Next and finally click Finish. At this point you would be able to launch a .NET Windows app from the server\folder you just set up in a code group and you would not get a security violation.

Creating the MSI script

On to creating the MSI script for this code group. Click on the runtime security policy. You should see an option for creating a deployment package. Click that option.

Next you should select the machine on the radio group and put in a local path and name for the MSI script.

Click Next and click Finish.

Conclusion

Now you have an MSI script that people can run, they only have to do it once, and they will be able to launch a Windows .NET app from a network server and they will not get a security violation. Note, when the MSI script is run, it will replace any current runtime security policies on the machine level. You can distribute the MSI script over Active Directory, but I will not include that in this article. I am planning on writing a third article on this subject. Since the main down side to distributing .NET apps on a network server is that, when you need to release a new version, if someone is in the app you can not copy over the new version, I have written an app that allows you to see all the files on a network server and who has those files open. That way you would know who to notify, that is who to ask to get out of the app so you can release it. (I do realize that if you have admin rights to the network server you have access to this information already, but for those of us who are not admins on the network server...).

Share

About the Author

I started my programmer career over 18 years ago doing COBOL and SAS on a MVS mainframe. It didn't take long for me to move into windows programming. I started my windows programming in Delphi (Pascal) with a Microsoft SQL server back end. I started working with vb.net when the beta 2 came out in 2001. After spending most of my programming life as a windows programmer I started to check out asp.net in 2004. I achieved my MCSD.net in April 2005. I have done a lot of MS SQL database stuff. I have a lot of experience with Window Service and Web services as well. I spent three years as a consultant programing in C#. I really enjoyed it and found the switch between vb.net and C# to be mostly syntax. In my current position I am programming in both vb.net and C#. Lately I have been using VS2012 and writing a Windows 8 app. You can search for the app it is called ConvertIT.

On a personal note I am a born again Christian, if anyone has any questions about what it means to have a right relationship with God or if you have questions about who Jesus Christ is, send me an e-mail. ben.kubicek[at]netzero[dot]com You need to replace the [at] with @ and [dot] with . for the email to work. My relationship with God gives purpose and meaning to my life.

Sorry I am just responding now. I did not get the notification when you posted your question.
I would guess you have already figured out the answers to you questions.

You asked:
Do I need to reboot network server after I make code group change?
No, there is no need to reboot the network server after the code group change.

Do I still need to create a MSI script to be run on each client server?
Yes, each client needs to trust the strong name so it will have the proper trust level when it accesses something on the network share.

Most of the time, if you still get the security error it is one of two things.
First, you didn't create the msi script correctly.
Second, you don't have admin rights on the PC that you ran the MSI script on.

My guess is that you don't have admin rights on the PC you ran the MSI script on. You don't get any indication that the MSI script didn't work. It just runs, but when you are not the admin it can't do anything since you don't have rights.

I've followed the process as you've described, created the code group, then created the MSI, and ran the MSI on a machine with administrator rights, and the could see the entry in the security.config file, and the application on the network share ran perfectly.

Then I ran the MSI on a machine without administrator rights. I received no errors when the installation ran. The applicatition on the share still failed with a security error, and when I checked the security.config file on this machine, there was no entry for the new code group to be found.

When you run the MSI script on the PC you must run it as an admin. Otherwise the script really doesn't do anything. There are some active directory ways of pushing out msi scripts to all PC's. A Network admin would probably have a better idea how to do that. Hope that helps.

Thanks for putting this together Ben. Definitely helps. Figured I'd add an extra piece to the puzzle for anyone interested.

This piece of code (which I cannot claim for myself) allows you to programmatically add your new codegroup to the machine policy settings. If you host your app on a server, throw this into a simple app and run it on each workstation using your method of choice.

Yes, you are correct. An admin will need to run this. Thanks for clarifying that. We're a pretty large installation and we'll probably push this out via SMS or however the windows admins will want to do it.

I'm glad you mentioned this. Thought I'd add my 2 cents regarding what I found. It appears that there is a number of differences with the security between 2003 and 2005. Using a couple highly simplistic apps, the one's created with 2003 would not run at all (without adding security), but the 2005 apps would. For example, I had an app that I created in VB.NET 2003 which had two forms containing some buttons and text boxes and that was it. I compiled it and attempted to run it from a shared network drive. It failed to run (debug stated it was failing at the end of sub new(). Interesting. Then I converted it to 2005 and attempted to run it from the same network drive. It actually ran. Very interesting. (I hope I explained that ok).

So what does this mean? Probably not much, but thought I'd share it with you. M$ definitely has revamped a lot of the security settings in 2005/2.0. I'm still going to have to stick with 2003 since we don't have framework 2.0 on all clients. However, I'm having to write to a temp file on the client, so I'm going to have to have a codegroup anyway.

I'm totally new to .Net and the company I work for are now making the move from vb6 to vb.Net. The code using the OpenFiles.exe sound more or less like what we need but it's a little over the top for me at this moment.
Basically I want to create a textfile log of all shared files accessed (over the network), when, for how long and by whom. So far your code is the closest I got to a solution.

Well, unfortunatly the openfiles.exe will only tell you that someone is in the files. You would probably need to write something that kept track of when the user first accessed the file and when they got out.

If you install the openfiles service on the network file share it will start to produce an XML file (which is a .net dataset) every minute that contains who is in what file. They overwrite after 60 minutes. The client app I wrote to go with it reads in the xml file and displays it.

So if you want to know when someone started looking at a file and how long they had it open, you would have to write some code that would process the xml file outputs and write out the correct txt files you need.

Sorry if this wasn't very helpful, I suppose sooner or later you will have to dig in and learn .net.

How lame would it sound if i put the following at the end of every one of my emails.

On a personal note I am a born again Muslim, if anyone has any questions about what it means to have a right relationship with Allah or if you have questions about who Mohammed is, send me an e-mail. spiritual_solicitor@inyourface.com My relationship with Allah gives purpose and meaning to my life, because of course, my relationship with my wife, kids, family, friends, and peers isn't enough.

Thanks for your note. I am sorry if my personal bio offends you. Still I make no excuses for it. The whole point of having a personal bio is so that you get to know someone a bit better. I can honestly say that as much as I enjoy programming, my relationship with God is so much more important. Yes even more then my wife, kids, friends. And you are correct that I believe that as much as I love and care for (wife, kids, friends) they can never satisfy me the way my relationship with God does.

Let me ask you this. If you found the Truth and it changed your life, wouldn't you want to share it with others? How could you keep your self from sharing what has happened in your life. I have always from an early age needed to know what is life all about? What is my purpose? I can tell you that only by a right relationship with God have I come to know True Peace and fulfillment. I can't help but say something so that others may know this same joy.

I wish you a blessed Ramadan and pray that as you seek God, He will reveal to you who Isa truly is, Son of God and Savior.

I've tried this with a small Managed C++ console app. I could never get the importing of the strong name to work. I allways get a error complaining about an invalid assembly. However running:sn -v myprog.exe

First I should tell you up front that my C++ skills leave something to be desired. So take my advise for what it is worth, which is pretty much just guessing. Anyway, I was guess that the part of the C++ code where you attach the strong name key to the C++ assembly isn't quite working. There you have it my best guess.

Thanks for your reply. I havn't got to the bottom of the problem yet, however I have got the strong name working. As far as I can tell, the key pair I generated is being compiled into the C++ program correctly, as the process is almost identicle as VB and C#.

What I did was make a very simple VB console program that does nothing except output some text and add the same key pair I was using for the C++ program. Then I used this exe to import the key pair into mscorcfg.

This worked, and now both the VB and C++ app work as expected. I think (not totally sure) all mananged C++ programs have an unmanaged stub, which might prevent importing the strong name key pair?

I have developed a Windows.Forms.UserControl, which is embeded in a Web page in Internet Explore. To work this control properly I have to create a group in Runtime Security Policy and give this group a permission set with granted 'Allow calls to Unmanaged Code' and 'Assert any permission that has been granted' permissions. When the Membership Condition is set with URL condition type, my control works as expected. But the case isn't the same when I have used the Strong Name condition type. IE just not activate the control, without any error messages or exceptions. In order to sign my assembly with strong name, I have created key pair with 'sn -k keypair.snk' command line, and added '[assembly: AssemblyKeyFile(@"..\..\keypair.snk")] line to my source code. After that I have verified the signature with 'sn -f ' comman line and it has told me that my assembli is valid. Can anybody explain me what is wrong?

First I will tell you straight up that I don't know the answer. Still I have some thoughts. Even though the assembly has a strong name you still need to create a code group where the strong name key is marked as trusted. Next it seems that every user that might access the page that uses that control may need to have that code group installed. If it is not running there then the web server itself would probably need the code group for the assembly to be marked at trusted.

Anyway, those are my thoughts. I hope they help you find the solution.

I have found the solution in
http://www.dotnet247.com/247reference/msgs/18/92236.aspx
Namely, adding the attribute
[assembly: AllowPartiallyTrustedCallers()] in AssemblyInfo.cs of my Control has resolved the problem. Really I don't know exactly why. Not yet.

I am new to vb.net but have written apps in vb6 which run from a central server. However have tried both part 1 and 2 suggestions without success. The debugging routine always points to the line which accesses an ini file. This file lives in the same folder on the server as the .exe

I need to use ini files to allow other users to easily edit settings. The registry is not an option.

I guess first I need to ask if you are working on your local pc? If you are then you need to double check the code group you set up. You probably have an error. You may not have set full trust. You might have not put a * in the network path name.

Now if it is working on your local PC, but not on a users PC. The issues I have seen are:
.net framework not loaded on a users PC. Load the .net framework.
They ran the MSI script, but it didn't add the code group. This usually happens when the user does not have admin rights to their own PC. Someone with admin rights must run the MSI script for them. You can use the RUNAS. I have also seen where after running the MSI script the User needs to log out and then log back in again.