Introduction
We all have worked on .NET and we know there are so many security features that are available. One among them is CAS (Code Access Security). Well, I am not going to discuss what CAS is and how it works? Here, I'll try to explain how is CAS helpful in providing security to our applications?
Description
Most of us are working and developing applications on enterprise level and the architecture that we follow is n-tier. In a typical scenario, we have a UI layer, business layer and a data layer or if we talk about design patterns then we have the MVC pattern which again talks about different layers. When we work on enterprise level it becomes necessary to secure the code libraries from the outside world. This is due to certain business rules in a business layer, or in the case of data some data rules which they don’t want anyone to know. Keeping this in mind, we will implement an example of how to secure our development layers.
Let’s create an assembly, Assembly1
. The code for Class1.cs is given below:
using System;
using System.Security.Permissions;
namespace Assembly1
{
public class Class1
{
public Class1()
{
}
public static string SayHello(string name)
{
return ("Hello " + name);
}
public static string MyData(string idTag)
{
return ("Did't Find " + idTag);
}
}
}
The code for AssemblyInfo.cs:
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile(@"..\..\..\key.snk")]
[assembly: AssemblyKeyName("")]
Now create a second assembly, Assembly2
(we will be using Assembly1
as reference):
The code for Class1.cs:
using System;
using Assembly1;
namespace Assembly2
{
public class Class1
{
public Class1()
{
}
public static string GetIDValue(string idTag)
{
return (Assembly1.Class1.MyData(idTag));
}
public static string SayHello(string name)
{
return(name + ", how can i help you");
}
}
}
The code for AssemblyInfo.cs:
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile(@"..\..\..\key1.snk")]
[assembly: AssemblyKeyName("")]
Here comes our application which is referencing Assembly1
. Create a form and add a button on it. On the click of a button add the following code:
MessageBox.Show(Assembly2.Class1.SayHello("User"));
MessageBox.Show(Assembly2.Class1.GetIDValue("I001G12"));
Don’t forget to add reference to Assembly2
in your Win Form application. So, in the end, the code is going to be something like this:
The code for TestAssembly
- Form:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics;
using Assembly2;
namespace TestAssembly
{
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
this.button1.Location = new System.Drawing.Point(112, 112);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new System.EventHandler(this.button1_Click);
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void button1_Click(object sender, System.EventArgs e)
{
MessageBox.Show(Assembly2.Class1.SayHello("User"));
MessageBox.Show(Assembly2.Class1.GetIDValue("I001G12"));
}
}
}
The code for AssemblyInfo.cs:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Permissions;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]
Once you are done try executing the application, well it will work without any issues. But now the question is: Are these assemblies secured? Just signing it with a strong name key doesn’t make it secure as any one can use this assembly in his application.
Let's take a second step where we use CAS - declarative approach for securing our classes. Now add the following code above your class1 code in Assembly1
:
[StrongNameIdentityPermissionAttribute(SecurityAction.LinkDemand, PublicKey =
"0024000004800000940000000602000000240000525341310004000001000100c39f40c9fccb7d"+
"cca6519852af805844bea03b3ddd6fa74677f7eba4687205045c47d72b95e08a91e666d7a2f01d"+
"483915cc87eb183f0a05b27900f1cfd7858d83da244dfffc021e9310f6049ac717ea89956bd22f"+
"439082e9822125b48c072dc6a487d2ce9e49931434023593b3d10a54f12b86591cb58919400102"+
"904e19d0")]
After adding the above code, try re-executing the application. The first message box is displayed but the second message box will throw a security exception to the user as the user is using a different key pair (key1.snk) to strong name Assembly2
(check the code of AssemblyInfo.cs). Now, change the Assembly2
key pair to Key.snk in assemblyinfo.cs and execute the code again. Now, everything will work the way it has to.
Let me explain what I did by adding the StrongNameIdentityPermissionAttribute
: this attribute helps in protecting class' public key pair of a strong name key used which is key.snk. And whenever an assembly, say X, references it to use the methods then X should have the same key available and used in the code, otherwise he won't be able to use our assembly.
In the above code, I have specified SecurityAction.LinkDemand
with a public key. LinkDemand
helps in walking down the stack walk and checking each assembly's evidence. If it matches it will allow and if not it throws an exception. I would have used SecurityAction.Demand
but Demand
checks the evidence of the immediate caller and in the above case it’s winform.dll which doesn’t have this evidence so it's going to fail. Well, you can check this by replacing LinkDemand
with Demand
and executing the Win Form application.
Let’s change the above example. Take out the intermediate assembly Assembly2
and use Assembly1
in your application and on the click of a button comment the first code and add these two lines:
MessageBox.Show(Assembly1.Class1.SayHello("User"));
MessageBox.Show(Assembly1.Class1.MyData("I001G12"));
Now execute the code. What do you see in the result? As soon as you click the button a security exception pops up as your application doesn’t provide the right evidence. Now in the assemblyInfo.cs add the key file as:
@"..\..\..\key.snk"</CODE>
And then execute the code again. Now it will work the way it has to. Well, I think I have cleared the point of making assemblies secure.
Try creating a console application with the same assemblies and instead of LinkDemand
use Demand
. Check the results, it will work perfectly. The reason is when we execute a Win Form application the winform.Dll works as an intermediate between the form and the assembly which creates the problem but for the console application it’s a straight way compilation and so there is no intermediate DLL to interact with. That’s why it works perfectly.
In the end, I would like to say "Happy coding"!
Currently i am working as a Technical Architect Microsoft .Net Practices in Sogeti USA, NY. I am one of the winner of Microsoft Architect Contest 2004. I have 8 years of experience in the IT industry. Doing R&D on new technologies and reading about them is one of my big time interests.