![]() |
Development Lifecycle »
Design and Architecture »
General
Intermediate
License: The Code Project Open License (CPOL)
A Code Protection Framework For .NetBy S. YingAn article on code protection and a flexible supporting framework for .Net languages |
Windows, Visual Studio, Dev
|
||||||||
|
Advanced Search |
|
|
|
||||||||||||||||
A software developer may find it necessary to "sell" some of his/her valuable mental skills and labor directly or indirectly to someone in order to make a physical and resulting mental living. The difference is in the "someone", be it his/her boss, a movement/cause or middle/end customers. The term "selling" used here does not necessary result in a cash return, it is used in a more general sense. For example, in open source community, a developer "sell" his/her contribution and get the benefit of resulting joy of mental achievement and a sense of belonging, the spirit of sharing with and help others, the right to improve or use others contribution, and some other technical advantages of such a mode of development. There is nothing in this article that is explicitly or implicitly against it, it is view in an orthogonal perspective. The technology developed at CryptoGateway that is capable of making the "selling" (in the above sense) to "someone" more manageable by right owners, e.g. the service providing sector of a open source community that owns a public license, and the "someone".
This article is on an access control technology for .Net (and possibly Java) assemblies and the supporting framework. It is an architectural overview, so it does not contain a complete program that can be used immediately. The framework proposed is general. It does not depend on the access control technology that requires it. Since the author is not familiar with Java programming, the rest of the article will use the .Net term for a particular concept.
An assembly in frameworks like .Net contain extensive self-reflective meta information about the type hierarchy contained in the assembly, which can be queried using the .Net framework itself. These information are a great language feature for designing, developing, manipulating (e.g. turning an operation into data of the meta system) and maintaining codes. It is not a welcome feature when the inner details of at least some assemblies in a software system are not indented to be exposed or publicized. Public or authorized sharing of code normally happen at the source code level, assemblies are not usually used to that end. This is because tools exist (or can be developed at low cost) that can be used to reverse-engineer the assemblies given the complete information. It is true that the current trend of software industry is shifting towards more of a service oriented type with the service provider holding most of the important assemblies on their servers. There is still a need to ship some of the assemblies to end users which are likely to contain assemblies that
One way of trying to achieve assembly protection is the use of code obfuscation. The other one, among others, is assembly encryption (with a digital signature as an additional option). The pros and cons of these methods will not be discussed here. If needed, these two techniques can be combined, especially when a non-intrusive encryption method, like the p2p data access control technology of us (see, e.g., 1, 2, 3, and the media player briefly discussed in this article), is employed to realize layered defense.
The framework that is considered to be good in general and is flexible enough to support the code protection technology and the resulting development culture should have, or is capable of been evolved into one that have, the following properties:
The host can act as a scripting engine for .Net languages. The unload capability provides a necessary condition. This is important for server applications and real-time debugging scenarios in which the host supports many child components during its life time that can not or is too expansive to be stopped, but some of these children needs to be changed (at the source code level!) in the meantime. There are many plug-in frameworks belonging to a variety of design patterns for .Net, this article is not going to discuss or compare any of them due partly to the length limit of the article. The way how the host realize the asynchronized message passing between its child components is also not to be covered here. A schematic UML diagram for how the child components are connected to host and each other that can satisfy the above requirements is shown in Fig. 1. There is a one to many relationship between the interface and the sockets and the components. A component is part of a socket. Each socket can allow a sequence of one to any components forming an ordered processing pipeline, to be plugged in. It is explained in more details in the following.
The first step in the attempt to connect a component to a host that is previously unknown to the host is to provide an interface that declares the properties and methods that are open to the host and a set of sockets (see the following).
Interfaces are introduced at least in two ways:
After the step one is done in a particular iteration or if the component implements a existing interface and either it is the first attempt to be plugged into the host or it is to be plugged in to a new kind of socket, then the socket has to be designed and implemented.
A socket acts as a dispatcher for call requests from the host to the component (sequence) derived from the same interface. It is also responsible for loading and unloading assemblies that contains the implement. It is initialized from a XML configuration "script" either in a execution branch reached prior to or just before one of the components handled by it is needed. The socket should be created in a separated AppDomain in order for the host to be able to unload the assembly set belonging to it. It should also be derived from MarchalByRefObject (or the equivalent of it) so that the host can communicate to it via object (remoting) proxies. The information initialized at this stage belongs to the socket class that is common to all instances of it. An instance of it is created in its own AppDomain to facilitate the the initialization (of static member variables) and is then discarded without creating any component. 6-7 inside the sequence diagram on the right (Fig. 2) represents the initialization process.
When the need to use one of the components arises, an instance of the socket that hold it is created first and then the corresponding set of assemblies are loaded by it. The load process, opaque to the host, is represented by call stack 9->10->11->12 in Fig. 2, which is discussed in the following The Secured Assembly loader is itself a socket holding the client of a process server inside crypto-gateway server where the authentication and decryption is performed. After the corresponding assembly is loaded, the set of instances of the types that is configured to be attached to the socket is created, which is represented by the call stack 13->14 in Fig. 2. After the socket is created, all the objects that is configured to attach to it are also created. The type of these objects and the objects themselves are used to access their members via reflection. More details is provided in the following.
The method call and property access can now be delegated to the component object (objects pipeline, if so configured) by the socket, which can also be configured to perform logging, exception trap and handling, etc.. The interface can also define a set of events for the components to call back to the host, which could 1) handling it; 2) delegate the call to other child components; 3) propagate it upward in the host hierarchy. The synchronized version of it is illustrated in Fig. 2 by 20->...->27. For asynchronized version, which is suggested above, call 20 returns (26->27) immediately after 21, leaving the host to handle the message. A typical socket call handler looks like
namespace Media
{
public class Mp3PlayerSocket : IStreamClient
{
.....................
public void LoadPlayList(System.Collections.ArrayList list)
{
try
{
if (!pipeline)
{
if (miLoadPlayList==null)
miLoadPlayList = otype.GetMethod("LoadPlayList",
new Type [] {typeof(System.Collections.ArrayList)});
miLoadPlayList.Invoke(o,new object [] {list});
}
else
{
foreach (string key in ttable)
{
otype = atable[key] as Type;
ArrayList item_list = new ArrayList();
miLoadPlayList = otype.GetMethod("LoadPlayList",
new Type [] {typeof(System.Collections.ArrayList)});
o = ol[key];
miLoadPlayList.Invoke(o,new object [] {item_list});
list.AddRange(item_list);
}
}
}
catch (Exception ex)
{
LogError(ex);
}
finally
{
................
}
}
private static MethodInfo miLoadPlayList = null;
..........
}
}
where IStreamClient is the interface. A typical socket configuration file looks like
<?xml version="1.0" encoding="utf-8"?>
<config>
<sactserver ip="127.0.0.1" port="1221" path="/" channelexpires="120" channelpersists="false" />
<holder>
<!--Auto generated nodes, edit with caution!-->
<assembly secured="true"
default="true" feature-index="0"
atoken="/MediaPlayer/MediaClients_0.ctk"
id="e6f511d2-f9f9-4ad6-b700-2505b92c64dc"
depend_id=""
name="MediaClients, Version=0.2.2059.28511, Culture=neutral, PublicKeyToken=null"
display="Media Clients">
<description />
<server ip="127.0.0.1" port="1221" />
<gui type="Media.Mp3Player,Mp3Player" show-background="images\Img12345.jpg" />
<interface type="Media.IStreamClient" wrapper="Media.Mp3ClientSocket" >
<type name="Media.Mp3Client" activate="true" ext=".mp3" descr="MP3 Audio"/>
<type name="Media.Equlizer" activate="false" ext=".mp3" descr="MP3 Filter"/>
</interface>
</assembly>
</holder>
</config>
The implementation is then performed. During the process a need may arises to go to a previous step.
Then the test is carried out. During the process a need may arises to go to a previous step.
If not, go to a previous steps, otherwise exit the process.
Most of design goals discussed above are realized and tested in various .Net programs of CryptoGateway that are in private use, under evaluation, or for public download. Although not a single one contains a realization of all of them, their combination does not have foreseeable obstacles.
The data access control technology of CryptoGateway is based on a declarative identity management and access right distribution system using cryptographic (RSA and AES) means. It can be applied to any digital data. Access right is distributed using soft tokens containing authentication information about the producer and the user through conventional communication channels like e-mail. The soft access token is secured using the same access control method. The production and distribution process is schematically shown in Fig. 3. For more details, interested read should read articles following the links given above.
They are not special in terms of crypo-images production, access token distribution and user access. More details on how this is accomplished is given here. What is special is the client, called "Secured Assembly Loader" in Fig. 1, of the process server hosted by the crypto-gateway server. It is specifically designed to communicate to the said server to load .Net assemblies, to connect to the authentication user interface, etc.. The one for the media player is written in C#. The process server do the actual job of authenticating the user. It also make sure that the data is indeed signed by the producer in the background. Valid data will be loaded only after both the user is authenticated and the producer's digital signature is verified.
After the data is load by the above mentioned loader, it will try to generate an assembly by calling
Assemb assembly = Assembly.Load(data);
Type type = assembly.GetType("...type name ...");
ConstructorInfo cinfo = type.GetConstructor(....);
object obj = cinfo.Invoke(args);
....
MethodInfo minfo = type.GetMethod(...);
minfo.Invoke(...);
....
PropertyInfo pinfo = type.GetProperty(...);
pinffo.GetValue(obj,...);
pinfo.SetValue(obj,...);
......
FieldInfo finfo = type.GetField(...);
finfo.GetValue(obj);
finfo.SetValue(obj,...);
......
where "data" is a byte array containing the serialized assembly data recovered by the process server. After the assembly is successfully constructed, reflection can be used to create instances and access their members. Unloading of these assembly is also possible by calling the Assembly.Unload() from the host after all thread in the components are stopped
In the design stage, various functional units used by the system should be partitioned into to a set of assemblies taking into account of the fact that .Net assemblies are not a MarshalByRefObject so each AppDomain must has its own version of an overlapping assembly. To reduce redundancy and potential inconsistency, an effort to reduce or eliminate the overlap is better to be made.
The factorization should also be geared towards logical separation of responsibilities. The host, as an upper layer, should treat various components in as little different ways as possible, which can be achieved by making the components as self-sustaining and self-describing as possible. This is called the principle of "maxium symmetry" in this article. A reader might encounter similar principle in studies on information/statistics related subjects under the name "maximum entropy" with its roots in statistic physics (try to search this site, a reader can find a few). "Symmetry", which is related to certain kind of invariance, is used because something is known is going to be put in a proper order. It is a better term for engineering. "Entropy", on the other hand, is related to things that are unknown that are going to be known or learnt. In fact better learning strategy also involves putting what's needed to be known next in a proper order, better engineering should also admit that there are unknowns. On artistic side the principle could result in greater simplicity in code. On practical side it leads to increased code reusability (invariance!), reduced maintenance cost and opened possibility for future expansion. The practice following this principle is also expected to result in a increase of code performance because passing messages (call request) through socket->host->sockets layers and AppDomain boudaries is not expected to be efficient.
Admittedly, the above formalization process generates a significant number of higher-level assemblies and classes (interfaces, sockets, configuration files, etc..) from classes where the real job is done. The synchronization between these classes in the iterative code development process is tedious and could be error prone. This pre-processing job can in fact be automated by taking advantage of the reflection capability of .Net and other frameworks. The use of software tools, like the x-script generator, can greatly simplify the tasks and reduce the possibility of code inconsistency. The post-processing, including compiling, signing of digital signature, encryption, initial access token distribution, registration, packing, uploading, etc., can also be streamlined using these tools too.
It can be expected that the application of the same idea to Java assembly (bytecode) is straightforward since these two frameworks share a lot of similarities in the aspects that are relevant to code protection and the plug-in (-out) framework introduced here.
As a reader already know, this article is written in a as general as possible form, it provides an ideal or background reference that hopefully will result in designs and structures that allow later transition to a more flexible system with less effort for those who use it. For simple applications, it is an overkill. In actual implementations, depending on the scale, flexibility, and future expansion requirements, a lot of the socket layers, interfaces definitions and code protection steps can be skipped or short circuited, at least in the early rounds/versions of the system, only taking into account of the possibility of expand it is required during the factorization process.
Continue expansion of the "etc.", etc..
| You must Sign In to use this message board. | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 25 May 2006 Editor: |
Copyright 2006 by S. Ying Everything else Copyright © CodeProject, 1999-2009 Web13 | Advertise on the Code Project |