How to Demand several StrongNameIdentityPermissions "at the same time" in 1.0 and 1.1.
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;
/*
This sample demonstrates how you can effectively check that all the callers of your method belong to a set of assemblies signed with StrongName keys you consider valid.
Effectively, this works pretty much as regular Demand for StrongNameIdentityPermission. However, regular Demand allows to query callers for one key only at a time, so if at least one of the callers posesses different key, normal Demand fails. This demo allows you circumvent this problem and "Demand" a set of keys at a time.
The idea put into it is very simple: using StackTrace class, we query all our callers, get their assembly Evidence, extract StrongName keys from them and then compare with the list of "good" keys.
This approach may not work if:
1. This code is not granted SecurityPermissionFlag.ControlEvidence that is needed to enumerate other's Evidence, and that in fact is quite powerful permission. However, if this sample is used to protect something so thoroughly, it may be already doing something that requires FullTrust.
2. Callers have ControlEvidence. In this case, they can spoof this mechanism, although it's quite not straighforward.
3. Machine has StrongName Verification skip entires that could be used to spoof the sample, too. But having such entries is asking for troubles anyway, so don't ever create them if you can avoid it.
Main method to use is EmulateSNDemand that takes a set of string representation of "good" keys and returns true if all callers have keys from this set, and fals if at least one caller did not have a key from this set.
Eugene V. Bobukh,
*/
[assembly: System.Reflection.AssemblyKeyFile("ST.snk")]
[assembly: AllowPartiallyTrustedCallersAttribute()]
[assembly:SecurityPermissionAttribute(SecurityAction.RequestMinimum, ControlEvidence = true)]
public class DemoClass
{
// Helper function.
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;
}
/*
Returns true if all caller's keys belong to passed set of GoodKeys, and fails otherwise. If you want to allow non-signed callers, add null to GoodKeys array. Second parameter shows how many stack frames to skip. You can use it if you don't want to bother checking your own key, for example. Zero means "check everyone". */
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;
// why bother further if at least one caller is "bad"?
}
#if VERBOSE
else Console.WriteLine("This key is \"good\"");
#endif
}
}
/* Finished searching. It appears that caller did not have a strongname, but let's check if we allow it: */
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
}
// If we are here, we've checked all the callers, and all they were "good", so return true:
return true;
}
/* This is a "protected" method that supposely wraps another, private method and calls it only if EmulateSNDemand() check passes. */
public static string ProtectionSample()
{
// Use sn.exe to extract these values from assemblies or key pairs:
string[] GoodKeys = {
/* Our's key. We can omit putting it here and set nFramesToSkip to 2 for not checking our own key it will prevent anyone from reverse-calling us. However, now we don't do it: */
"0024000004800000940000000602000000240000525341310004000001000100f93aeade2ce364"+
"726aa2f1110c4d927f729145c198c02563ad4735fa4ecdee9dc4029b48596b3f3adfece384c05a"+
"24dadf1afe8c6277f2eddb9831a5465ac85bc05d46ea62975e2ecde4ae1c023c53676a0b17c5ba"+
"dcc22cfb9569866c375147023ec5c4dd92346328e25573f049938f3c85ced6685eeb2df9355c5c"+
"210c38bb",
/* Key 1 */
"0024000004800000940000000602000000240000525341310004000001000100a5c58607f9d289"+
"ba1a7e80ceb24e4f56bd110290f0a4d6f6eadc1687efc3db3fc972e3c06be0f287a39b65a54bd9"+
"007853f5e1d773f3179fd1af588684f71de16c8df6ec4c460d4cbe8bdb5651a2bf8afc8760aabf"+
"eca8822913f19c9d38f87111e00f616082db10c6547d37b714193c4dd682b8f55c38438727710f"+
"204bcdf8",
/* Key 2*/
"0024000004800000940000000602000000240000525341310004000001000100b91d308addb513"+
"c492a56462e5f582adc93c7b9b841a19342bce653848e74aace749d86e53ce126ddb6d543b063b"+
"044c4b0d31574a0f82a834eccf1c580c086308fc77cc615bc07f168826aae5e0f1e87d485c8142"+
"2715f341af54d88036435f01e0bc85e30daa864993e26888c0df72ee4c9c5795d5fc9f8e7dfc08"+
"894ab1b0",
// For demo purposes, we allow nonsigned callers, too.
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";
}
}
Comments
- Anonymous
March 10, 2004
Are you sure that this works? The PublicKey.ToString does not work as you would expect. I blogged about it here: http://weblogs.asp.net/rmclaws/archive/2004/03/04/84293.aspx - Anonymous
March 10, 2004
Well, at least it used to work :) I guess the reason for confusion is that I'm using not the PublicKey [which is essentially just a byte array in your sample, right?] but StrongNamePublicKeyBlob.ToString() here -- it's different. Hope that helps :) - Anonymous
April 27, 2004
hi