Problem Statement:
Code Access Security provides developers with numerous ways of protecting their methods from unauthorized or untrusted callers, including usage of caller's StrongName signature to identify it.
So if one would like to make sure that all the callers of some method are signed with particular key [what is almost equivalent to being shipped by particular publisher], he/she would probably do something like this:
[method: StrongNameIdentityPermissionAttribute(SecurityAction.Demand, PublicKey = "0x002400...")]
public int ProtectedMethod()
{
//..
}
However, in practice this approach is not feasible, because almost always there are some other callers on the stack. Even though they may be considered valid, [e.g., code from System.dll, or unmanaged IE "frame"], they possess different public keys and thus fail the Demand.
So typically people use LinkDemand instead of normal Demand, what obviously gives much lesser protection as:
a) LinkDemand checks only immediate caller, thus opening the door for luring attack;
b) It is enforced during Jit-time only.
Additionally, it quite poorly solves the problem of handling several "valid" callers. In CLR version 2.0 there is a workaround for this, but in earlier versions people have to create a LinkDemand-protected wrapper for each caller key per each protected method. Such a hassle!
Proposed Solution:
However, even CLR 1.0 posesses classes that may be used to emulate full Demand functionality. The sample below shows how to do it.
[Disclaimer: although I verified that it builds and runs for me, I do not accept any responsibility for consequences of using this code or any parts of it]
Note, that you may want to replace hardcoded public keys in the sample. Use "sn.exe -Tp" to extract them from assemblies, and "sn.exe -tp" from key files.
/************************************************************/
// Comment this out if you don't want detailed output:
#define VERBOSE
using System;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics;
[assembly: System.Reflection.AssemblyKeyFile("ST.snk")]
[assembly: AllowPartiallyTrustedCallersAttribute()]
[assembly:SecurityPermissionAttribute(SecurityAction.RequestMinimum, ControlEvidence = true)]
public class DemoClass
{
private static bool IsInSet(StrongName SN, string[] GoodKeys)
{
string Key;
if (null == SN) Key = null;
else Key = SN.PublicKey.ToString();
foreach (string s in GoodKeys)
{
if ((null != s)&&(null != Key))
{
if (Key.ToUpper() == s.ToUpper()) return true;
}
else if (s == Key) return true;
}
return false;
}
public static bool EmulateSNDemand(string[] GoodKeys, int nFramesToSkip)
{
StackTrace st = new StackTrace();
int Frames = st.FrameCount;
Console.WriteLine("Frames: {0}", Frames);
for (int i = nFramesToSkip; i < Frames; i++)
{
StackFrame sf = st.GetFrame(i);
MethodBase mb = sf.GetMethod();
Type t = mb.DeclaringType;
Assembly Asm = t.Assembly;
Evidence Ev = Asm.Evidence;
#if VERBOSE
Console.WriteLine("===================================");Console.WriteLine("Looking at the caller #{0} in the stack", i);
Console.WriteLine("Calling method: {0}", mb);
Console.WriteLine("Class that contains it: {0}", t);
Console.WriteLine("Calling Assembly: {0}", Asm);
#endif
bool bKeyFound = false;
foreach (object o in Ev)
{
if (o is StrongName)
{
bKeyFound = true;
#if VERBOSE
Console.WriteLine("Caller has StrongName:\r\n{0}", ((StrongName) o).PublicKey.ToString());
#endif
if (false == IsInSet((StrongName) o, GoodKeys))
{
#if VERBOSE
Console.WriteLine("This key is not \"good\"");
#endif
return false;
}
#if VERBOSE
else Console.WriteLine("This key is \"good\"");
#endif
}
}
if (false == IsInSet((StrongName) null, GoodKeys))
{
#if VERBOSE
Console.WriteLine("Caller does not have a key, and this is not allowed");
#endif
return false;
}
#if VERBOSE
if (false == bKeyFound) Console.WriteLine("Caller does not have a key, but we allowed this");
#endif
}
return true;
}
public static string ProtectionSample()
{
string[] GoodKeys = {
"0024000004800000940000000602000000240000525341310004000001000100f93aeade2ce364"+
"726aa2f1110c4d927f729145c198c02563ad4735fa4ecdee9dc4029b48596b3f3adfece384c05a"+
"24dadf1afe8c6277f2eddb9831a5465ac85bc05d46ea62975e2ecde4ae1c023c53676a0b17c5ba"+
"dcc22cfb9569866c375147023ec5c4dd92346328e25573f049938f3c85ced6685eeb2df9355c5c"+
"210c38bb",
/* Key 1 */
"0024000004800000940000000602000000240000525341310004000001000100a5c58607f9d289"+
"ba1a7e80ceb24e4f56bd110290f0a4d6f6eadc1687efc3db3fc972e3c06be0f287a39b65a54bd9"+
"007853f5e1d773f3179fd1af588684f71de16c8df6ec4c460d4cbe8bdb5651a2bf8afc8760aabf"+
"eca8822913f19c9d38f87111e00f616082db10c6547d37b714193c4dd682b8f55c38438727710f"+
"204bcdf8",
/* Key 2*/
"0024000004800000940000000602000000240000525341310004000001000100b91d308addb513"+
"c492a56462e5f582adc93c7b9b841a19342bce653848e74aace749d86e53ce126ddb6d543b063b"+
"044c4b0d31574a0f82a834eccf1c580c086308fc77cc615bc07f168826aae5e0f1e87d485c8142"+
"2715f341af54d88036435f01e0bc85e30daa864993e26888c0df72ee4c9c5795d5fc9f8e7dfc08"+
"894ab1b0",
null
};
if (true == EmulateSNDemand(GoodKeys, 0)) return "\r\nAll callers have \"good\" keys";
return "\r\nSome of the callers do not appear to have \"good\" keys";
}
}