By now you've probably already created your first "Hello World"
application using one of the languages in the .NET Framework such as C# or
VB.NET or perhaps you've even managed VC++. The .NET Framework allows all
kinds of different languages to utilize code written in various other
languages and by other vendors. But there's a downside: how do you make sure
your code isn't used by unauthorized clients?
There's been an enormous amount of marketing and publicity surrounding
the .NET Framework and all the wonderful things it can do for you. If you
browse the Web at all, you simply can't escape the onslaught of advertising
and marketing related to how XML Web services will change the future of
programming and the future of the Web.
One of the features that Microsoft uses as a selling point for their
adventurous new framework is the concept of Code Access Security (CAS).
Every piece of code executed by the Common Language Runtime (CLR) runs
within a security context. Such code is granted permissions based on various
identifying factors that are also the elements that contribute to the strong
name of an assembly. They are:
Public Key: A key that's assigned to your assembly when you build it against an RSA signature file
It is the last of these, the public key, that we'll focus on in this
article.
Keeping Out Unauthorized Users
Administrators can execute a security policy that grants or revokes
permissions at various levels throughout the enterprise to assemblies based
on any of the above identifying characteristics. This allows an
administrator to prevent a given assembly from contacting hosts on the
Internet or from deleting files from the local hard drive.
It's fine that administrators can control what code is allowed to do
and what it's not allowed to do on their own machines. But problems arise
when the developers and the administrators have a conflict of interest.
Take the following scenario: you're the publisher of some software that
reads and writes its data in an encrypted, proprietary format. This is
advantageous to you because only your software can read and write this data.
Now, along comes .NET and you convert your software to .NET and distribute
it. Let's say you have a class library that manages all of the I/O with
regard to your proprietary file type. Well, with command-line utilities like
ILDASM.EXE and programmatic tools using Reflection, anyone can simply
examine the metadata for that assembly and get a detailed description of all
methods and their arguments. It would take a programmer only a few minutes
to determine how to use your assembly to manipulate your proprietary files.
Obviously, you don't want this to happen. There are countless other
scenarios in which companies need to protect their own interests by
preventing their APIs and classes from being executed by unauthorized users.
Strong-Naming an Assembly
It might look bad, but you do have an option. Earlier, we mentioned that
you can strong-name an assembly by signing it with an RSA signature file.
This creates a public/private key pair and stores that pair in a file. All
assemblies built using this signature file will expose the same public key,
which allows them to be identified as coming from the same publisher. No one
can produce an assembly with the same public key unless they manage to get
access to your RSA signature file.
To create this RSA signature file, open up a command prompt (preferably
the one Visual Studio .NET provides, which automatically configures your
path settings) and type the following:
SN k SecureProducts.snk
For our examples, we're going to work with a fictitious company called
Secure Products, Inc. They produce APIs they want to restrict so only code
written by Secure Products, Inc., can use them.
I mentioned earlier that signing all your assemblies with the same
signature file effectively marks them as having come from the same
publisher. Let's create a sample assembly and sign it with the
SecureProducts.snk file.
To do this, open up Visual Studio .NET and create a new Class Library
project (in C#). Call the project SecureAssembly and leave the default
namespace and filename alone. Then open up the AssemblyInfo.cs, set the
version number to 1.0.0.0, and modify the AssemblyKeyFile attribute to point
to the file we created.
You should end up with two attribute lines that look like this:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyKeyFile(@"..\..\SecureProducts.snk")]
This gives the linker enough information to properly strong-name your
assembly. Make sure you copy the SecureProducts.snk file to the root
directory of the SecureAssembly project; otherwise, VS.NET won't compile it.
Now we have an assembly that's strongly named and, theoretically,
digitally signed as having come from Secure Products, Inc. Let's remove the
default Class1.cs class and add our own. We'll call it SecuredClass.
Add a method to SecuredClass called GetTopSecretInformation (not very
original, but it'll do for our purposes). Here's the code for that method:
public class SecuredClass
{
public SecuredClass()
{
}
public string GetTopSecretInformation()
{
return "The neon blue eagle " +
"flies at dawn. The turtle has landed.";
}
}
We don't want the contents of this string to fall into enemy hands.
Therefore, we need to use a feature of CAS to demand that only code with the
public key belonging to Secure Products, Inc., can instantiate this class or
use it in any way.
To do this, we need to obtain the full public key for Secure Products,
Inc. In order to do that, we'll use another command-line utility called
secutil. This allows us to extract security information from already
compiled assemblies. Get your command prompt, go to the SecureAssembly\obj\
debug directory, and type the following:
Secutil hex strongname SecureAssembly.dll > secutiloutput.txt
This will create a text file that looks a little like this:
Microsoft (R) .NET Framework SecUtil 1.0.3512.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
Public Key =
0x00240000048000009400000006020000002400005253
41310004000001000100EB8C810B9B
24A636F7FFCA28693AB03DA926839C0FCC5C2B4F0A94CE
0C94EB9690F519AC71AAC3C38F9AAD
AD460DEC33BFD8678B8199322BDC586F757339E6CE88DD
ED22B03466F8AB2205C75E8406A0F4
0C33F7FC11F06C73A43C95B21DD9E9ABD27C5B994605423
3FF5C6B37D89690696CCDB41D7E74
F8B59E66FB5EA1059E
Name =
SecureAssembly
Version =
1.0.0.0
Success
Now we're going to highlight the entire public key hex string (except
the prefix of 0x, which is redundant since we know the string is in hex) and
copy it to the clipboard.
Then, we'll use the public key we copied to place the following
attribute at the top of our SecuredClass class definition, as follows:
[StrongNameIdentityPermission(SecurityAction.LinkDemand,
PublicKey="002400000480000094000000
06020000002400005"+
"25341310004000001000100EB8C810B9B2
4A636F7FFCA28693"+
"AB03DA926839C0FCC5C2B4F0A94CE0C94E
B9690F519AC71AAC"+
"3C38F9AADAD460DEC33BFD8678B8199322
BDC586F757339E6C"+
"E88DDED22B03466F8AB2205C75E8406A0F
40C33F7FC11F06C7"+
"3A43C95B21DD9E9ABD27C5B9946054233F
F5C6B37D89690696"+
"CCDB41D7E74F8B59E66FB5EA1059E")]
public class SecuredClass
What we're doing here is informing the CLR that any attempt to access
this class (both static and object instances) must have the public key we
specify (hence the LinkDemand enumeration value).
If not, the CLR will throw an exception. There's no way to create an
assembly that has this public key without possessing the private key
belonging to it and that resides in our SecureProducts.snk file.
Therefore, keep your signature files secure.
Security Test
Now, to prove my point and to make sure this is more than just smoke and
mirrors, I'll create a console application that references this assembly.
I'll use it to create an instance of the SecuredClass class and try to print
out the contents of the top-secret string. Here's my basic code for the
console application:
static void Main(string[] args)
{
//
// TODO: Add code to start application here
// SecureAssembly.SecuredClass
secClass = new SecureAssembly.SecuredClass();
Console.WriteLine("Top Secret Information is: {0}",
secClass.GetTopSecretInformation());
}
I build the project as-is (without digitally signing it!) and run it.
Figure 1 is a screenshot of the output. As you can see, the results aren't
pretty and we don't get a chance to execute the method we wanted.
Now I'll create another console application, but this time I'll make
sure that the AssemblyInfo.cs file contains the following:
[assembly: AssemblyKeyFile
(@"..\..\..\SecureAssembly\SecureProducts.snk")]
Using my directory structure, this attribute points directly to the
SecureProducts.snk file used to sign the SecureAssembly. Setting my version
to 1.0.0.0 and building, I then run the application and receive the output
shown in Figure 2.
The moral of the story is that while the .NET Framework allows all kinds
of different languages to utilize code written in various other languages
and by other vendors, there is a downside. If you want to make sure that
your code doesn't get used by unauthorized clients, then you'll need to do
something similar to what I've done here.
Security Loophole?
The other thing you should be concerned with is the visibility of
constant strings in your code. For example, bring up your command prompt, go
to the \SecureAssembly\obj\debug directory, and type:
ILDASM SecureAssembly.dll
With a little bit of navigation and clicking, we get to the method
definition in IL (Intermediate Language) shown in Figure 3.
Well, isn't that handy? Our super-secret private information is there
in plain view for everyone to see. Why would we need the public key or
digital signature if we can just peel the information right out of it with
ILDASM, a tool that ships for free to anyone who wants it?
Unfortunately there's no easy answer here. If you're worried about
constants in your code being visible to the wrong people, you'll need to
encrypt or obfuscate those constants. The innards of all of your assemblies
should be considered open books to those who know how to open them.
Summary
Hopefully, the information I've given you here will help you maintain
some semblance of security on your assemblies. By using the technique of
demanding a public key to execute your code, and additionally encrypting or
obfuscating all the string literals in your code, you should be able to
produce extremely tamper-proof, secure assemblies.
Author Bio
Kevin Hoffman has been programming since he was 10 and has written
everything from DOS shareware to n-tier, enterprise Web applications in VB,
C++, Delphi, and C.
kevin_hoffman@cch-lis.com
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com