Partially Trusted Clients

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(  );
   }
   public new void Close(  )
   {
      base.Close(  );
   }
   void IDisposable.Dispose(  )
   {
      Close(  );
   }
}

Client-Side Demands

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.

Suppressing demands with PartialTrustClientBase<T>

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.

Raw WCF demands with PartialTrustClientBase<T>

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 USERNAME

Windows security with alternative credentials

Security permission to control principal, Environment permission to read USERNAME

Diagnostic tracing

Security permission with unmanaged code, Environment permission to read COMPUTERNAME

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.

Structured demands with PartialTrustClientBase<T>

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 (AsyncPattern), Username credentials, Message security with service cert negotiation and without service cert validation

Security permission to control policy and evidence

Windows security with interactive user credentials

Environment permission to read USERNAME

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 COMPUTERNAME, File I/O permission to path discovery, append, and write to log files

Analyzing the demands of PartialTrustClientBase<T>.Invoke( )

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.

Implementing client-side structured demands

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.

Tip

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
{...}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.178.181