To enable clients at any partial-trust level to use any WCF feature and binding, you
need to block the bindings' demand for full trust. The only way to do that is to have the
proxy itself assert full trust. Asserting full trust can easily be done via the PermissionSetAttribute
, using the Assert
flag of the SecurityAction
enum and
specifying the string "FullTrust" for the permission name:
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
In addition, you must prevent the client from directly accessing any method of the base
class of ClientBase<T>
(which still demands full
trust), so the proxy needs to hide the commonly used methods Close(
)
and Dispose( )
. Having the proxy class
itself access methods or properties of ClientBase<T>
(such as Channel
or
constructors) is fine, since the proxy asserts full trust. The problem is that in order to
assert full trust, the proxy itself must be granted full trust, which is something the
partially trusted client is not able to provide in the first place. Consequently, you need
to factor out the proxy class to its own assembly, mark it as public
, and grant that assembly full trust. In .NET 2.0 and later, you can
grant the proxy's assembly full trust using the Configuration control panel applet by
identifying the assembly using some content-based evidence, such as its strong name. You can
also install the proxy assembly in the client's GAC. Since all assemblies coming from the
GAC are granted full trust, the proxy will also thereby gain full trust. You also need to
allow partially trusted callers to the assembly using the AllowPartiallyTrustedCallers
attribute. Finally, you need to add to the proxy's
assembly the definition of the contract used by the proxy (and mark the contract as public
as well). This is required because WCF demands full trust
of all assemblies up the call chain, and if the contract comes from a partially trusted
assembly the demand will fail. Example D-1 shows
such a contract and such proxy definitions.
Example D-1. Asserting full trust by the proxy
[assembly: AllowPartiallyTrustedCallers] [ServiceContract]public
interface IMyContract { [OperationContract] void MyMethod( ); }[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
public
class MyContractClient : ClientBase<IMyContract>,IMyContract,IDisposable { public MyContractClient( ) {} public MyContractClient(string endpointName) : base(endpointName) {} /* More constructors */ public void MyMethod( ) { Channel.MyMethod( ); } publicnew
void Close( ) { base.Close( ); } void IDisposable.Dispose( ) { Close( ); } }
The problem with the technique shown in Example D-1 is that it is a potential security breach.
Suppressing the security demands of WCF means that any partially trusted client can now
call any WCF service. Consider a client that was not granted permissions to connect to a
TCP socket or a website. While that client will not be able to use sockets or HTTP
programming directly, it will be able to bypass that limitation by calling out from the
restricted client environment over WCF. The solution for that is to use a dedicated
subclass of ClientBase<T>
that on the one hand
will assert the blank WCF demand for full trust, and on the other will demand the
appropriate specific security permissions according to what the client is trying to do. My
PartialTrustClientBase<T>
class, shown in Example D-2, is such a proxy class.
Example D-2. The PartialTrustClientBase<T> class
public abstract class PartialTrustClientBase<T> : ClientBase<T>,IDisposable where T : class { [PermissionSet(SecurityAction.Assert,Name = "FullTrust")] public PartialTrustClientBase( ) {} [PermissionSet(SecurityAction.Assert,Name = "FullTrust")] public PartialTrustClientBase(string endpointName) : base(endpointName) {} [PermissionSet(SecurityAction.Assert,Name = "FullTrust")] public PartialTrustClientBase(Binding binding,EndpointAddress remoteAddress) : base(binding,remoteAddress) {} //Useful only for clients that want full-brunt raw demands from WCF protected new T Channel { [PermissionSet(SecurityAction.Assert,Name = "FullTrust")] get { return base.Channel; } } [PermissionSet(SecurityAction.Assert,Name = "FullTrust")] new public void Close( ) { base.Close( ); } void IDisposable.Dispose( ) { Close( ); } protected virtual void Invoke(Action action) { if(IsAsyncCall(action.Method.Name)) { DemandAsyncPermissions( ); } DemandSyncPermissions(action.Method.Name); CodeAccessSecurityHelper.PermissionSetFromStandardSet( StandardPermissionSet.FullTrust
).Assert( )
; action( ); } protected virtual R Invoke<R>(Func<R> func) { if(IsAsyncCall(func.Method.Name)) { DemandAsyncPermissions( ); } DemandSyncPermissions(func.Method.Name); CodeAccessSecurityHelper.PermissionSetFromStandardSet( StandardPermissionSet.FullTrust
).Assert( )
; return func( ); } protected virtual void DemandAsyncPermissions( ) { CodeAccessSecurityHelper.DemandAsyncPermissions( ); } protected virtual void DemandSyncPermissions(string operationName) { this.DemandClientPermissions(operationName); } bool IsAsyncCall(string operation) { if(operation.StartsWith("Begin")) { MethodInfo info = typeof(T).GetMethod(operation); object[] attributes = info.GetCustomAttributes( typeof(OperationContractAttribute),false); Debug.Assert(attributes.Length == 1); return (attributes[0] as OperationContractAttribute).AsyncPattern; } return false; } }
PartialTrustClientBase<T>
is used just like
the regular proxy base class. You still need to grant full trust to the proxy class
derived from it and allow partially trusted callers. However, unlike the code in Example D-1, PartialTrustClientBase<T>
does not assert full trust at the class
level. Instead, it asserts full trust locally, just when required. In addition, PartialTrustClientBase<T>
may be used to demand
code-access security permissions.
If you derive your proxy class from PartialTrustClientBase<T>
and have the proxy assert full trust as in
Example D-3, no demands will be placed on
the calling client.
Example D-3. No demands from PartialTrustClientBase<T>
[PermissionSet(SecurityAction.Assert
,Name ="FullTrust"
)] public class MyContractClient : PartialTrustClientBase<IMyContract>,IMyContract { public MyContractClient( ) {} public MyContractClient(string endpointName) : base(endpointName) {} public void MyMethod( ) { Channel.MyMethod( ); } }
The only difference between Example D-3
and Example D-1 is that Example D-3 is cleaner, since the hiding of
Close( )
and Dispose(
)
is now done by PartialTrustClientBase<T>
. The proxy in Example D-3 still suppresses all security
demands by WCF, potentially enabling partially trusted clients to do more than intended
and exposing them to luring attacks.
The more security-conscious use of PartialTrustClientBase<T>
is not to assert full trust by the proxy,
as shown in Example D-4.
Example D-4. Allowing raw WCF security demands
public class MyContractClient : PartialTrustClientBase<IMyContract>,IMyContract
{
public MyContractClient( )
{}
public MyContractClient(string endpointName) : base(endpointName)
{}
public void MyMethod( )
{
Channel
.MyMethod( );
}
}
To support such use, PartialTrustClientBase<T>
surgically asserts full trust on its
constructors and its Close( )
method. In addition,
PartialTrustClientBase<T>
hides the Channel
property of ClientBase<T>
and asserts full trust on the get
accessor. This is sufficient to suppress the WCF binding's demand for
full trust, since that demand is made when constructing, opening, and closing the proxy,
not when actually using it. The interesting effect of structuring the proxy this way is
that now the client code will be subjected to the raw WCF security demands—that is, all
the security demands required to marshal the call to the service!
For example, if the proxy is using the TCP binding, the proxy will first demand of
the client permission to execute (all managed code requires that permission). Second,
the proxy will demand of the client permission to connect to the port on the service
machine, and unrestricted DNS permissions (required to resolve the host's address).
Third, there are some collateral permission demands unrelated to the use of TCP,
pertaining to the context of the call. If the client wishes to use Windows security and
send the user's interactive identity, the proxy will demand environment permission
access to the USERNAME
variable. If the client wishes
to send alternative Windows credentials, the proxy will demand security permission to
control the principal. If the client wants to dispatch calls asynchronously or receive
duplex callbacks, the proxy will demand permissions to control the policy and the
evidence (both are flags of the security permission, required when bouncing calls
between threads). If the client wishes to use reliable messaging, the proxy will demand
policy control as well. If the client uses Message security with username credentials,
with service certificate negotiation and without validating the negotiated service
certificate, the proxy will additionally demand permission to control the policy and the
evidence. If the client utilizes the WCF diagnostics and tracing facility, the proxy
will demand access to the COMPUTERNAME
environment
variable (to be able to trace it) and unmanaged code access (presumably to access the
log files, so this should actually have been a file I/O permission instead). Finally,
the proxy will demand of the client permission to execute unmanaged code.
Unmanaged code access is a highly privileged security permission, granted only to the most trustworthy code. Granting this permission may amount to disabling code-access security, since unmanaged code is exempted from code-access security. Classes and frameworks designed to work in a partial-trust environment never demand unmanaged code access (even if they use interop); instead, they demand a more narrow permission describing the nature of the unmanaged operation to be performed. The TCP channel (or the pieces of WCF it uses) demands unmanaged code access simply because its designer never thought it would be used by a partially trusted client as in Example D-4. There are also certain WCF capabilities that bluntly resort to full-trust demands, even though there are perfectly matching permissions types. For example, any attempt to propagate a transaction requires full trust (instead of the use of the distributed transaction permission), and any access of a certificate store requires full trust (instead of the use of the certificate store permission). Table D-2 shows the raw WCF security demands invoked by code such as that in Example D-4 when the bindings are set to their default settings, and the permissions demanded by a few key scenarios, such as transactions, reliability, diagnostics, asynchronous calls, certificate store access, and Message security.
Table D-2. Raw WCF client-side demands
Scenario |
Permissions |
---|---|
TCP |
Security permission with execution and unmanaged code, unrestricted DNS permission, Socket permission to connect to port on target host |
IPC |
Security permission with execution, unmanaged code, control policy, and control evidence |
WS and WS-Dual |
Security permission with execution and unmanaged code, Web permission to connect to URI |
Basic and web |
Security permission with execution, web permission to connect to URI |
MSMQ |
Security permission with execution and unmanaged code |
Asynchronous calls, duplex over TCP |
Security permission to control policy and evidence |
RM over TCP |
Security permission to control policy |
Windows security with interactive user credentials |
Environment permission to read |
Windows security with alternative credentials |
Security permission to control principal, Environment permission to read
|
Diagnostic tracing |
Security permission with unmanaged code, Environment permission to read
|
Username creds, Message security with service cert negotiation and without service cert validation |
Security permission to control policy and evidence, Store permission to enumerate certificates |
Username creds, TCP Message security with service cert negotiation and without service cert validation |
Security permission to control policy and evidence |
Any certificate store access, Transaction propagation |
Full trust |
Username creds, Message security without service cert negotiation or with service cert validation, Certificate creds |
Full trust |
The only bindings that do not demand unmanaged code access are the basic and web bindings. The WS binding defaults to Message security, resulting in an unmanaged code-access permission demand.
As you can see from Table D-2, key valuable aspects of WCF (such as transactions, reliable messaging, unlimited Message security interaction, and certificate store access) all require full trust, thus rendering code such as that in Example D-4 pointless in a partial-trust environment. Furthermore, the demand made for unmanaged code access by virtually all of the non-HTTP bindings (and the WS binding with Message security) is unacceptable, negating the very idea of code-access security and partially trusted clients.
To enable appropriate, secure, and correct use of WCF by partially trusted clients,
PartialTrustClientBase<T>
offers two Invoke( )
methods, defined as:
protected virtual void Invoke(Action action); protected virtual R Invoke<R>(Func<R> func)
The Invoke( )
methods accept a delegate to
invoke. That delegate wraps the operation call, and the difference between the two
methods is in the need to return a value. Example D-5 shows a proxy deriving from PartialTrustClientBase<T>
using the Invoke( )
methods.
Example D-5. Generating structured demands with PartialTrustClientBase<T>
public class MyContractClient : PartialTrustClientBase<IMyContract>,IMyContract
{
public MyContractClient( )
{}
public MyContractClient(string endpointName) : base(endpointName)
{}
public void MyMethod1(int number)
{
Invoke(( )=>Channel.MyMethod1(number));
}
public bool
MyMethod2(int number)
{
return Invoke(( )=>Channel.MyMethod2(number));
}
}
The Invoke( )
methods will first demand the
appropriate code-access security permissions according to the scenario of the calling
client and the target service endpoint. If the client is granted those permissions—that
is, no security exception is raised by the demands—the Invoke(
)
methods will programmatically assert full trust and proceed to invoke the
requested operation, satisfied that the client has the right permissions to call the
service. I call such behavior structured permission demands.
The Invoke( )
methods cannot use an attribute to
declaratively assert full trust, since that would mask out any permissions they demand.
Instead, the implementation of the Invoke( )
methods
in Example D-2 first checks whether the call
was invoked asynchronously (using the AsyncPattern
flag of the operation contract) and then, if so, demands the appropriate permissions
using the DemandAsyncPermissions( )
helper method.
The Invoke( )
methods then demand the synchronous
permissions using the DemandSyncPermissions( )
helper
method. For the call itself, the Invoke( )
methods
programmatically assert full trust using my CodeAccessSecurityHelper
class:
public enum StandardPermissionSet { Internet, LocalIntranet, FullTrust, Execution, SkipVerification } public static class CodeAccessSecurityHelper { public static PermissionSet PermissionSetFromStandardSet( StandardPermissionSet standardSet); public static void DemandClientPermissions<T>(this ClientBase<T> proxy, string operationName) where T : class; public static void DemandAsyncPermissions( ); //More members }
The PermissionSetFromStandardSet( )
method takes
an enum value representing one of the standard .NET permission sets and returns the
matching instance of PermissionSet
:
public interface IStackWalk { void Assert( ); void Demand( ); void Deny( ); void PermitOnly( ); } public enum PermissionState { None, Unrestricted } public class PermissionSet : IStackWalk,... //More interfaces { public PermissionSet(PermissionState state); public IPermission AddPermission(IPermission permission); public void Assert( ); public void Demand( ); public static void RevertAssert( ); //More members }
PermissionSet
, as its name implies, is a
collection of permissions, but it can also represent the super-permission set FullTrust
(which is not really a set of individual permissions; it is more like a single
permission). The PermissionSet
class supports the
IStackWalk
interface, which lets you install a
stack-walk modifier, such as a modifier that stops the demand for the permissions in the
permission set by asserting that all callers up the stack have those permissions. The
stack-walk modifier is removed automatically when the method that installed it returns.
You can also explicitly remove it with the static RevertAssert(
)
method of PermissionSet
.
Both the DemandAsyncPermissions( )
and DemandSyncPermissions( )
helper methods of PartialTrustClientBase<T>
use corresponding methods on
CodeAccessSecurityHelper
to make their
demands.
Table D-3 shows the structured
demands raised by the PartialTrustClientBase<T>.Invoke(
)
methods as a function of the binding used and other aspects of the
scenario, such as the use of transactions, reliability, certificate store access,
diagnostics, callbacks, and asynchronous calls.
Table D-3. Structured security demands of PartialTrustClientBase<T> with default binding values
Scenario |
Permissions |
---|---|
TCP |
Security permission with execution, unrestricted DNS permission, Socket permission to connect to port on target host |
IPC |
Security permission with execution, control policy, and control evidence |
WS, Basic |
Security permission with execution, Web permission to connect to URI |
WS-Dual |
Security permission with execution, Web permission to connect to URI and accept callbacks to callback address, minimal ASP.NET hosting permission |
MSMQ |
Security permission with execution, MSMQ permission to send to queue |
RM over TCP |
Security permission to control policy |
Duplex over TCP, Asynchronous calls ( |
Security permission to control policy and evidence |
Windows security with interactive user credentials |
Environment permission to read |
Windows security with alternative credentials |
Security permission to control principal |
Transaction propagation |
Unrestricted distributed transaction permission |
Username credentials, Message security without service cert negotiation or with service cert validation, Certificate credentials |
Store permission to enumerate stores, open stores, and enumerate certificates |
Diagnostic tracing |
Environment permission to read |
I based the structured demands of PartialTrustClientBase<T>.Invoke( )
on a few elements. First,
whenever possible, I tried to approximate the raw demands on the client raised by WCF,
as shown in Table D-2. That said, I did smooth WCF's
rough edges (since it was not designed for comprehensive partial-trust use). None of the
bindings and scenarios in Table D-3
demand full trust or unmanaged code access. Second, there are plenty of places in .NET
that were designed for use in partial-trust environments in similar contexts, so where
appropriate I relied on the same demands as those. Finally, in the other cases, to
compensate for suppressing the full-trust demands, I used experience, familiarity with
code-access security, and common sense to map WCF activities to demands for dedicated
permission types.
When the WS-Dual binding is used, the Invoke( )
methods demand web permission to connect to the target endpoint, as with any other HTTP
binding. However, to allow for hosting the callback object, they also demand minimal
ASP.NET hosting permission and web permission to accept the calls to the callback
address.
When the MSMQ binding is used, the Invoke( )
methods demand MSMQ permission to send messages to the target queue.
With any attempt to propagate the client's transaction to the service, the Invoke( )
methods demand unrestricted distributed
transaction permission. This is the case when a transaction-aware binding is used, when
transaction flow is enabled in the binding, when transaction flow at the operation level
is allowed, and when the client has an ambient transaction.
Any attempt by the proxy to access the certificate store triggers demands for permissions to enumerate the certificate stores, to open a store, and to enumerate the certificates in a store. This will happen when the client uses certificate credentials, when Message security is used and the client needs to validate the negotiated service certificate, or when the client does not negotiate a certificate and instead just loads a certificate to use for securing the message.
When the client uses WCF diagnostics, the Invoke(
)
methods demand environment permission to read the computer name and file
I/O permissions against the log and trace files used.
As mentioned already, the demands are carried out by CodeAccessSecurityHelper
, whose partial implementation is shown in Example D-6.
Example D-6. Implementing CodeAccessSecurityHelper (partial)
public static class CodeAccessSecurityHelper
{
public static PermissionSet PermissionSetFromStandardSet(
StandardPermissionSet standardSet)
{
PermissionSetAttribute attribute =
new PermissionSetAttribute(SecurityAction.Demand);
attribute.Name = standardSet.ToString( );
return attribute.CreatePermissionSet( );
}
internal static void DemandAsyncPermissions( )
{
IPermission permission = new SecurityPermission(
SecurityPermissionFlag.ControlEvidence|SecurityPermissionFlag.ControlPolicy);
permission.Demand( );
}
public static void DemandClientPermissions<T>(this
ClientBase<T> proxy,
string operationName)
where T : class
{
DemandClientConnectionPermissions(proxy.Endpoint);
DemandTransactionPermissions(proxy.Endpoint,operationName);
DemandTracingPermissions( );
DemandClientSecurityPermissions(proxy);
DemandEnvironmentPermissions(proxy);
DemandClientStorePermissions(proxy.Endpoint);
}
internal static void DemandClientConnectionPermissions(ServiceEndpoint endpoint)
{
PermissionSet connectionSet = new PermissionSet(PermissionState.None);
if(endpoint.Binding is NetTcpBinding)
{
connectionSet.AddPermission(new SocketPermission(
NetworkAccess.Connect,TransportType.Tcp,
endpoint.Address.Uri.Host,endpoint.Address.Uri.Port));
connectionSet.AddPermission(new DnsPermission(
PermissionState.Unrestricted));
}
/* Rest of the bindings */
connectionSet.Demand( );
}
internal static void DemandTransactionPermissions(ServiceEndpoint endpoint)
{
DemandTransactionPermissions(endpoint,null);
}
internal static void DemandTransactionPermissions(ServiceEndpoint endpoint,
string operationName)
{
bool transactionFlow = false;
bool flowOptionAllowed = false;
if(endpoint.Binding is NetTcpBinding)
{
NetTcpBinding tcpBinding = endpoint.Binding as NetTcpBinding;
transactionFlow = tcpBinding.TransactionFlow;
}
/* Checking other bindings */
if(transactionFlow)
{
if(Transaction.Current != null)
{
//If operationName is null, then at least one operation
//needs to allow flow to trigger demand
foreach(OperationDescription operation in endpoint.Contract.Operations)
{
string name = operationName ?? operation.Name;
if(name != operation.Name)
{
continue;
}
TransactionFlowAttribute attribute =
operation.Behaviors.Find<TransactionFlowAttribute>( );
if(attribute == null)
{
continue;
}
if(attribute.Transactions != TransactionFlowOption.NotAllowed)
{
flowOptionAllowed = true;
break;
}
}
if(flowOptionAllowed)
{
IPermission distributedTransactionPermission =
new DistributedTransactionPermission(
PermissionState.Unrestricted);
distributedTransactionPermission.Demand( );
}
}
}
}
//Rest of the implementation
}
All permission types in .NET support the IPermission
interface:
public interface IPermission : ... { void Demand( ); //More members }
Demanding any permission is done by instantiating the permission object and calling
its implementation of the Demand( )
method. This is
exactly what the DemandAsyncPermissions( )
method
does to demand the permission to control the policy and the evidence when invoking a
call asynchronously. When constructing a permission set, you can add individual
permissions to it and then call Demand( )
on the
permission set to demand all permissions in the set. The extension method DemandClientPermissions( )
of CodeAccessSecurityHelper
does the bulk of the demands on behalf of PartialTrustClientBase<T>
(using an extension enables
using it with any proxy class). It has a series of helper methods, all demanding
permissions for their respective aspects. Example D-6 shows the code for DemandClientConnectionPermissions( )
, used to demand
connectivity permissions according to the binding. It examines the binding type used by
the proxy and adds the appropriate permissions (according to Table D-3) to a permission set object. It
then calls Demand( )
on the permission set. The
DemandTransactionPermissions( )
method first
verifies that the proxy is using a binding capable of transaction flow. If so, it
verifies that the calling client actually has an ambient transaction to propagate (i.e.,
that Transaction.Current
is not null
). If so, it scans the collection of operations for the
endpoint's contract, looking for the operation currently invoked. When it finds it,
DemandTransactionPermissions( )
retrieves that
operation's collection of operation behaviors and looks for the TransactionFlowAttribute
. If the attribute is configured to allow
transaction propagation, DemandTransactionPermissions(
)
demands the distributed transaction permission.
In a similar manner, DemandClientPermissions( )
uses other helper methods to demand the appropriate permissions.
To enable partially trusted clients and callbacks, I defined the class PartialTrustDuplexClientBase<T,C>
. This class is
used and implemented very much like PartialTrustClientBase<T>
, except it adds duplex support:
public abstract class PartialTrustDuplexClientBase<T,C> : DuplexClientBase<T,C> where T : class {...}
18.216.47.169