We need our users to run some legacy software on their own computers in order to work with a service of ours. They do this with one small function of a large software package. But we want to update the behavior of that function pretty frequently, without forcing the users to keep downloading and installing updates for the whole package. Enter Microsoft’s ClickOnce technology. It’s a very nice way to give users a near-web experience but with near-native software capability.
ClickOnce programs are launched from a link on a web page, self install (and can self update), but run in the .Net environment under Windows. Unlike web pages they can access Windows APIs to do whatever you really need. Which is a potentially big security problem. Just clicking on a link could really open your computer to anything, so Windows restricts what ClickOnce programs can do, and makes sure that users approve anything at all risky.
Some of the user approval requests can be very scary; we want to configure things to make those notices as clear and unalarming as possible. Signing code helps placate Windows. The alert window borders change from angry orange to soothing baby blue, the wording does less warning and more asking whether to allow something, and Windows says that it knows who the code comes from, instead of just an unknown or unverified publisher. The concept of code signing isn’t too hard to follow, but I found the mechanics difficult, in part because the consequences of different scenarios aren’t well described; I had to try them all and see what happened. This post describes what eventually worked, with a few asides about what didn’t.
The user starts with a web page that does most of the work that’s needed. But there is a step that must be performed on his or her own PC, for security reasons. So the web page contains a link to a ClickOnce application to do that work. The ClickOnce application needs to run a legacy, console mode native Windows application to do the work. ClickOnce applications are not allowed to do that under any circumstances, so far as I can tell, so the ClickOnce application launches a hidden .Net application that then launches the legacy native application.
It all looks kind of like this:
When the user clicks the link in the web page, he or she may get a security alert before the program is downloaded, installed and run. It depends on the exact version of Windows, user privilege level, security policies, and probably the phase of the moon. In any case, if the alert is shown we want it to be as unthreatening as Windows will allow.
The hidden .Net application has to be built with a manifest specifying that it has to run with Administrative rights in order to launch the native application. When it starts, Windows might display a really scary alert because of that. Again, we want to make this as benign as Windows will let it be.
Curiously (to me) starting the native application doesn’t trigger any alerts, even though that’s got the most potential to cause trouble. I guess it’s because the hidden .Net application already got permission to escalate to the highest possible privilege level. After that, Windows must figure that anything goes.
The ClickOnce Application
When it’s just built and deployed normally, the first time the user runs it he or she sees something like this:
In Visual Studio 2008 (even the Express editions) you can right click on the project name and select Properties to get a tabbed page about the project. The Signing tab lets you “Sign the ClickOnce manifests“. You’ll need a certificate, but Visual Studio will offer to create a test certificate for you to use. However, it has no effect on this warning. Even though the code is now signed, the certificate wasn’t issued by a valid certificate authority, so Windows doesn’t much care.
Now, if you get a paid code signing certificate, things are a bit different. We got one from Thawte. In the Signing tab I just clicked the check box for “Sign the ClickOnce manifests” but then, instead of clicking the button labeled Create Test Certificate… I clicked on Select from Store… and picked our paid certificate. When I run the published application from a web page after that, there’s no warning at all. That’s what I want. I’m not as sure as Microsoft that just having a valid certificate is enough to make me trustworthy, but combining that with the execution restrictions placed on ClickOnce applications makes this a pretty safe operation for the user.
The Hidden Application
This is a regular .Net program, not a ClickOnce application. It will be included as a delivered resource in the ClickOnce application, and that application will launch it from the ClickOnce private storage area. Launching any application this way might trigger warnings, but this application definitely does. That’s because the app.manifest for this program has the entry:
<requestedExecutionLevel level=”requireAdministrator” uiAccess=”false” />
When the ClickOnce application launches the hidden application Windows makes sure that the user knows about it. I’d love to show you an image of the alert but in Windows 7 all other software, including the Print Screen key, is disabled while the warning is up. The alert has an angry orange heading that says “Do you want to allow the following program from an unknown publisher make changes to this computer?” The default answer is No, and there’s a Help me decide link to more information that doesn’t provide reassurance. Well, it shouldn’t be reassuring. The program has enough privilege to cause big trouble if it wants to. But our users have been downloading, installing, and running similar programs from us for years, so they’ve already decided to trust us. Our problem is to keep this alert from scaring them away.
Code signing helps. Once the hidden program has been signed, there’s still an alert, but it’s not nearly as frightening. The alert has a baby blue background and says “Do you want to allow the following program to make changes to this computer?” It lists the program name, and our company name as the Verified Publisher. If the user clicks Show Details, he or she can examine the certificate itself. And the Help me decide link displays text that’s more reassuring:
If the program has a verified publisher, it means that the program has a valid digital signature. A digital signature helps ensure that the program is what it claims to be and comes from a reputable publisher. If the program has an unknown publisher, it doesn’t have a valid digital signature from its publisher. This doesn’t necessarily mean the program is harmful—many older, legitimate programs lack signatures.
It not only strongly implies that we are a “reputable” publisher, it also gives the user the idea that legitimate programs lack signatures only if they are “older”. Since our program is brand new (to the user), that would be taken as another red flag if it weren’t signed.
The Native Application
This isn’t an issue. When the hidden program launches it, Windows doesn’t show any alerts. It just works without any special code signing.
How to Sign
As mentioned above, signing the ClickOnce application is very easy. Get your certificate from a known Windows certificate authority, and install it in your Windows certificate store. The procedures for doing those first two steps vary from provider to provider. But once it’s in the local certificate store, you can sign your code automatically with every build by doing the following steps once:
- Right-click on the project name in the Solution Explorer pane, and select Properties from the context menu. (This is not at all the same properties that show up as a child of the project in solution explorer.)
- Click on the Signing tab.
- Check the box Sign the ClickOnce manifests.
- Click the Select from Store… button, find your purchased certificate in the list, select it, and click OK.
- (Optional) Fill in the Timestamp server URL with a value provided by your certificate vendor.
I have to take it on faith that step 5 above will do anything. The problem it’s designed to solve is what happens when someone runs your ClickOnce application after your certificate expires. So long as the application was built and signed before the expiration, the signature should still be considered valid and Windows should remain happy to run the program. But how can it know that it was signed then? A trusted timestamp server can be used to sign your code with an added timestamp. That way, Windows can know that the signature was applied while the certificate was still valid. The only way I can see for myself that using a timestamp server makes a difference is to create one build with it and one without, and then wait a year for my certificate to expire. Well, I could play with my system clock, but for all I know, Windows checks that value over the network, too.
Signing the hidden .Net application is a little harder. The Signing pane in Visual Studio says it’s just for ClickOnce. Depending on what you do here, you might prevent Visual Studio from building the program at all (because it will think it’s a ClickOnce application, and they aren’t allowed to request administrative privilege), or it might just have no visible effect at all. There’s a check box to Sign the assembly instead, which seems like it’s what I want, but in my tests it either didn’t work (complaining that the key file I chose was already installed, so I couldn’t use it) or had no visible effect. So I used the command line signtool that’s available as part of the Windows Platform SDK for Windows 7 (or any of the ones for other recent versions of Windows).
To sign a program with signtool you need your certificate and private key in a pfx file, not the certificate store. If you specified that the key should be exportable when it was installed in Windows then you can export it from the store directly to the needed file. Otherwise, you’ll have to follow instructions from your certificate provider. For example, Thawte delivered the certificate I was using as two files: mycert.spc and privatekey.pvk. I had to create a pfx from those two files with the command pvk2pfx:
pvk2pfx -pvk privatekey.pvk -pi oldpassword -spc mycert.spc -pfx signcert.pfx -po newpassword -f
In this command, oldpassword is whatever password the certificate issuer places on the pvk file, and newpassword is anything I want to use to protect the new pfx file.
Regardless of where the pfx file comes from, once you have it you can use signtool:
signtool sign /f signcert.pfx /p newpassword hidden.exe
If you want a timestamp, signtool can do that, too:
signtool timestamp /t http://somebigca.com/some/path hidden.exe
You can automate this in Visual Studio:
- Right click on the hidden application’s project in Solution Explorer and select Properties from the context menu.
- Click on the Build Events tab.
- Put the signtool commands in the Post-build event command line box. You can refer to the executable using the macro “($TargetFileName)”.
- (Optional) Add a copy command to the Post-build event command line box after the signtool statements to copy the built executable to the ClickOnce project’s folder. If you use Solution Explorer to Add this file to the deployment package for the ClickOnce application it will be delivered every time a user updates the ClickOnce application.
Managing the code signing certificates in a multiple developer environment is going to be tricky. You don’t want a developer who leaves the company to be able to take a usable copy of the certificate with him or her. The best way I can think of to manage this is to have a highly trusted person install the certificate on each developer’s PC, marking it as not exportable. Then anyone who logs in to that PC can use the certificate on the PC, but (supposedly) can’t take a copy to a different machine.
That approach won’t work for the signtool examples used for the hidden application above, because they expect the certificate to be in a file, which anybody could copy. It appears that signtool can directly use a certificate from the Windows certificate store instead of a file, which would reduce or eliminate the chance of a developer copying it. I may give that a try in a day or two, after I clear my head of this project by working on something else for a while.