TransportWithMessageCredentials – I need to know who is knocking on my door.

The point to be noted here is that even though the security facts of comminication like integrity and confidentiality is taken care of by the transport we might not get enough information from the client as to “Who are you?”. Now for this case you need to add in some credentials about the clients.

It is exactly for this that the security mode of transport with message credentials might come in handy. Now when using SSL for security, mostly for IIS hosted apps you might require to send the user credentials, windows identity,username etc. This basically can be achieved by specifying the binding as follows.  

      <wsHttpBinding>
        <!– configure wsHttp binding with Transport security mode
                   and clientCredentialType as None –>
        <binding name=”Binding1″>
          <security mode=”TransportWithMessageCredential”>
            <message clientCredentialType=”Windows”/>
            <transport clientCredentialType=”None”/>
          </security>
        </binding>
      </wsHttpBinding>

 The point is that there is no credentials required on the transport and the credentials can be send at the message level and WCF would actually identitify the windows credentials used and you can check the ServiceSecurityContext of the current operation context and obtain the identity of the user. 

The other points to be noted in this sample http://msdn2.microsoft.com/en-us/library/ms751427.aspx that shows transport security is that the certificate has to be setup on IIS. Please not that

  1. PermissiveCerticificatePolicy.Encat has to happen for the process to use the sample certs that are created by the scripts and you
  2. the same SSL certificate has to be set up.

I have modified the sample to show 2 scenarios, one with windows credentials and the other with a custom usename validator.

TransportWithMessageCredentials – UserName – Windows.zip (42.72 kb)

Adding a Message Header without using a MessageContract

Using Message contracts is quite a straight forward way of creating and adding message headers.But then again you might just want to add a header without going downt that route like this post by [Kenny]. You generally come across 2 scenarios where you want to add headers in every message or just for a particular set of operations that you would like to control. The code below shows you how to add headers for every operation using a Client behavior or just to a particular set calls using the OperationContextScope which would give you access to current operation context on the client side.

You can modify the outgoing message headers on using the OperationContext. If you are using a client instance(generated proxy then you can uset he inner channel ref: article) or else you can just use the channel factory and get the client channel.

Here the first operation is called without the a scoped header and the header is added only after the first call to the proxy happens. and the same operation can now access the header newly added header.

using System;

using System.Collections.Generic;

using System.Text;

using System.ServiceModel;

using System.ServiceModel.Description;

using System.ServiceModel.Channels;

using System.ServiceModel.Dispatcher;

 

namespace SoapHeaderSample

{

 

    [ServiceContract]

    public interface ICalculatorService

    {

        [OperationContract]

        int Add(int a, int b);

    }

 

    public class CalculatorService : ICalculatorService

    {

        public int Add(int a, int b)

        {

            //Get the header in the operation

            MessageHeaders hdrs = OperationContext.Current.IncomingMessageHeaders;

            int hdrIndex = hdrs.FindHeader(“BehaviorHeader”, “BehaviorHeaderNS”);

            int scopeHdrIndex = hdrs.FindHeader(“ScopedHeader”, “ScopedHeaderNS”);

 

            Console.WriteLine(“HeaderContent at service:” + hdrs.GetHeader<string>(hdrIndex));

 

            if(scopeHdrIndex >=0)

                Console.WriteLine(“ScopedHeader Content at service:” + hdrs.GetHeader<string>(scopeHdrIndex));

           

            return a + b;

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            string addr = “net.tcp://localhost:5050/ServiceModelSamples”;

            Uri[] baseAddrs = new Uri[] { new Uri(addr) };

 

            using (ServiceHost host = new ServiceHost(typeof(CalculatorService), baseAddrs))

            {

                host.AddServiceEndpoint(typeof(ICalculatorService), new NetTcpBinding(), “”);

                host.Open();

 

                Console.WriteLine(“Host listening at “ + host.BaseAddresses[0]);

                Console.ReadLine();

 

                //Call the service

                ChannelFactory<ICalculatorService> cf = new ChannelFactory<ICalculatorService>(new NetTcpBinding(), addr);

                cf.Endpoint.Behaviors.Add(new EndpointBehaviorAddHeader());

                ICalculatorService proxy = cf.CreateChannel();

                try

                {

                   

                    using (OperationContextScope scope = new OperationContextScope(proxy as IClientChannel))

                    {

                        Console.WriteLine(“tResult “ + proxy.Add(10, 100));

 

                        //Add the new header for this operation

                        Console.WriteLine(“nNew Header added.n”);

                        OperationContext.Current.OutgoingMessageHeaders.Add(

                            MessageHeader.CreateHeader(“ScopedHeader”, “ScopedHeaderNS”, “Scoped header value”));

                        Console.WriteLine(“tResult “ + proxy.Add(10, 100));

                    }

                }

                finally

                {

                    IChannel c = proxy as IChannel;

                    if(c !=null && c.State == CommunicationState.Opened)

                        c.Close();

                }

 

            }

        }

 

        public class EndpointBehaviorAddHeader : IEndpointBehavior

        {           

            public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)

            {}

 

            public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

            {

                clientRuntime.MessageInspectors.Add(new MessageInspectorAddHeader());

            }

 

            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

            {}

 

            public void Validate(ServiceEndpoint endpoint)

            {}

        }

 

        public class MessageInspectorAddHeader : IClientMessageInspector

        {

            public void AfterReceiveReply(ref Message reply, object correlationState)

            {}

 

            public object BeforeSendRequest(ref Message request, IClientChannel channel)

            {

                MessageHeader hrd = MessageHeader.CreateHeader(“BehaviorHeader”, “BehaviorHeaderNS”, “BehaviorHeader header content”);

                request.Headers.Add(hrd);

                return null;

            }

        }

    }

}

 

 

Checking the SIDs in the WindowsClaimSet

In continuation to my post of SAM vs PP, we concluded that to avoid fractured policy checking we can still check if the user belongs to a particular group by checking the occurence of an SID in the WindowsClaimSet that he submits to the service.

One of the problems that I faced to view the SID of an object was in the SDDL format for direct comparison. The Sid is basically represented as a SDDL string. If someone can point me to some proper tool besides these, it would be greatly helpful. these are the one’s i used.
You can use GetSID.exe that is a part of the the support tools that ship with Windows 2003 to find the SID as SDDL string. Or there is another tool PSID from SysInternals that helps you get this string pretty easily and both are command line tools.

Once you get this value you can compare the Claim Set for the occurence of this SID to figure out if the user provided a claim.
 

using System;
using
System.Collections.Generic;
using
System.Text;
using
System.ServiceModel;
using
System.IdentityModel.Policy;
using
System.Collections.ObjectModel;
using
KBE.Service.Diagnostics;
using
System.Security.Permissions;
using
System.Security.Principal;
using
System.IdentityModel.Claims;
using
System.Configuration;

 

public class CustomServiceAuthorizationManager : ServiceAuthorizationManager
{
    protected override bool CheckAccessCore(OperationContext operationContext)
    {
        System.Diagnostics.Debug.WriteLine(“CheckAccess — Started” + Utility.GetUserDetails());
        try

        {
            foreach (ClaimSet cs in OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets)

            {
                if (cs is WindowsClaimSet)
                {
                    foreach (Claim c in cs)
                    {
                        if (c.ClaimType == ClaimTypes.Sid)
                        {
                            SecurityIdentifier sid = c.Resource as SecurityIdentifier;

                            if (sid != null)
                            {
                                // Check if the SID is a claim for the group
                               
if (sid.Value == ConfigurationManager.AppSettings[“AuthorizationPermissionRole”])
                                    return true;

                            }
                       }
                    }
                }
            }
            return false;

        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(“tException : “ + ex.Message);
            throw;
        }
        finally
       
{
            System.Diagnostics.Debug.WriteLine(“CheckAccess — Ended”);
        }
    }

    protected override ReadOnlyCollection<IAuthorizationPolicy> GetAuthorizationPolicies(OperationContext operationContext)
    {
        return base.GetAuthorizationPolicies(operationContext);
    }
}

Configuraiton Entry

<appSettings>

  <add key=AuthorizationPermissionRole value=Some SID String/>

</appSettings>

 

 

ServiceAuthorizationManager and PrincipalPermission

You may face a problem when trying to check for Principal permission and demand in the CheckAccessCore of the SerivceAuthorizationManager and you might see a security exception. This is primarily because the threads principal is not set when this demand check in the SAM happens.

 

You can however do a Principal Permission check within the operation either by a Demand() operation for the principal or delcaratively in code. This what Brent Schmaltz who helped with this issue said.

“There are two advantages of the SAM approach it is:

  1. centralized and every call will be routed through there.  This avoids what I like to call the ‘fractured policy system” where one needs to touch all access points to understand the authorization policy.  This is similar to the File System.  For example with this method it is difficult to answer:  Can X access Y, without having X attempt to access Y?
  2. called way up the stack and is hence has a performance advantage.”

 

The solutions was to check the WindowsClaimSet. This claim set holds all the SIDs that is required and we can check the claim set if the SID of the group you require exits and bump the user out and authorizer the user using this. Basically you have a collection of SecurityIdentifiers in the WindowsClaimSet.

Next time – Checking SID’s in the WindowsClaimSet

Web Hosted Sample with UserNamePassword Supporting Token

In the Passing a UserName as a supporting token post was pretty much a single console app to demo the basic flow. This sample is a bit more richer and shows a web hosted service that requires the usernameSecurity token.

To configure it please create a app vdir in IIS and point the client on that location. Sorry about not including the setup script for the sample.

The message inspector and the validator and all done in the service host but the endpoint is configured in the config. So basically I have wired up what was left off with the config. The service host factory just takes the first endpoint it finds and slaps on the requirement of the supporting token. This enables applying this factory onto any service type.

Basically the client pretty much remains the same. The only difference here is that the I have retrieved the Name claim from the default claim set and returned it to the user just to show that this is indeed passed as a claim and not as a paramter :)

Passing a UserName as a supporting token.

Firstly i would like to thank Brent Schmaltz who helped me solve this problem.  When trying to secure messages might require more than than the primary token to identity the client. We can then resort to sending additional information that would help in identitification or some kind of custom processing.

 

Basically the code below, from Brent, shows how to use a windows token to send a supporting user name token. Sometimes the user name on another system might not be your windows creds and so this would help in flowing the expected id to the service. The windows token would be used to secure the user name token in this scenario.

 

Basically what we do is secure the binding using wsHttpBinding and then add a UserNameSecurityTokenParameters object into the security binding element.Do note the he’s specifed a custom validator to validate this information and specify the UserNamePasswordValidationMode to custom.

 

 

 

using System;

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.ServiceModel.Security.Tokens;

using System.ServiceModel.Security;

using System.IdentityModel.Selectors;

using System.IdentityModel.Tokens;

using System.IdentityModel.Claims;

using System.IdentityModel.Policy;

using System.Security.Cryptography.X509Certificates;

using System.Security.Principal;

using System.Threading;

using System.Collections.Generic;

using System.Collections.ObjectModel;

 

namespace Ideas

{

    class WindowsAuthDefaultClean

    {

        public static void Main()

        {

       

            string baseAddress = “http://localhost:8001/WindowsAuthDefault”;

            EndpointAddress epa = new EndpointAddress(baseAddress);

 

            // Create the binding

            WSHttpBinding wsHttpBinding = new WSHttpBinding(SecurityMode.Message);

            wsHttpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

            wsHttpBinding.Security.Message.EstablishSecurityContext = false;

 

 

            // Create the service host and attache the custom u/p validator]

            // and the service authorization manager.

            ServiceHost sh = new ServiceHost(typeof(WorkService), new Uri(baseAddress));

            sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;

            sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();

            sh.Authorization.ServiceAuthorizationManager = new CustomServiceAuthorizationManager();

       

 

            // Get the security element to add the parameter requirements.

            SecurityBindingElement sbe = null;

            BindingElementCollection bec = wsHttpBinding.CreateBindingElements();

            sbe = bec.Find<SecurityBindingElement>();

 

            if (sbe == null)

                throw new InvalidOperationException(“no SecurityBindingElement found”);

 

            sbe.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UserNameSecurityTokenParameters());

            CustomBinding cb = new CustomBinding(bec);

            sh.AddServiceEndpoint(typeof(IWorkContract), cb, baseAddress);

            sh.Open();

 

 

            Console.WriteLine(“Service Listening on : “ + sh.BaseAddresses[0] + “nPress <ENTER> to call service.”);

            Console.ReadLine();

 

 

            //Create the client.

            ChannelFactory<IWorkContract> cf = new ChannelFactory<IWorkContract>(cb, epa);

 

            // validated in CustomUserNameValidator

            cf.Credentials.UserName.UserName = “test1″;

            cf.Credentials.UserName.Password = “1tset”;

 

            IWorkContract wc = cf.CreateChannel();

            try

            {

                Console.WriteLine(wc.DoWork(“Get to work”));

            }

            catch (CommunicationException e)

            {

                Console.WriteLine(e);

            }

            catch (Exception e)

            {

                Console.WriteLine(“Exception: {0}”, e.ToString());

            }

 

            Console.WriteLine(“nPress <ENTER> to terminate”);

            Console.ReadLine();

        }

    }

 

    public class CustomUserNameValidator : UserNamePasswordValidator

    {

        // This method validates users. It allows in two users, test1 and test2

        // with passwords 1tset and 2tset respectively.

        // This code is for illustration purposes only and

        // MUST NOT be used in a production environment becuase it is NOT secure.

        public override void Validate(string userName, string password)

        {

            if (null == userName || null == password)

            {

                throw new ArgumentNullException();

            }

 

            if (!(userName == “test1″ && password == “1tset”) && !(userName == “test2″ && password == “2tset”))

            {

                throw new SecurityTokenException(“Unknown Username or Incorrect Password”);

            }

        }

    }

 

    public class CustomServiceAuthorizationManager : ServiceAuthorizationManager

    {

        protected override bool CheckAccessCore(OperationContext operationContext)

        {

            return true;

        }

 

        protected override ReadOnlyCollection<IAuthorizationPolicy> GetAuthorizationPolicies(OperationContext operationContext)

        {

            return base.GetAuthorizationPolicies(operationContext);

        }

    }

   

   

}

 

    [ServiceContract]

    public interface IWorkContract

    {

       [OperationContract]

       string DoWork(string input);

    }

   

   public class WorkService:IWorkContract

   {

       public string DoWork(string input)

       { return “Input params “ + input;}

   }

 

/*

 * Config solution for setting up custom U/P validator

 *

    <behaviors>

      <serviceBehaviors>

        <behavior name=”CalculatorServiceBehavior” includeExceptionDetailInFaults=”True”>

          <serviceCredentials>

            <!–

            The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.                      

            –>

            <userNameAuthentication userNamePasswordValidationMode=”Custom” customUserNamePasswordValidatorType=”Microsoft.ServiceModel.Samples.CalculatorService+CustomUserNameValidator, service” />

            <!–

            The serviceCredentials behavior allows one to define a service certificate.

            A service certificate is used by a client to authenticate the service and provide message protection.

            This configuration references the “localhost” certificate installed during the setup instructions.

            –>

            <serviceCertificate findValue=”localhost” storeLocation=”LocalMachine” storeName=”My” x509FindType=”FindBySubjectName” />

          </serviceCredentials>       

        </behavior>

      </serviceBehaviors>

    </behaviors>

*/

 

Next - Web Hosted sample that implements the supporting token requirements.

Disabling Anonymous Authenticaiton on IIS for Message Security and Impersonation

When hosting WCF services in IIS we would ideally want to disable anonymous authenticaion on a website when there are other resources or type of endoints being hosted on that site. The solution to this is not quite obvious.The problem basically is that we have a situation where there are double identities coming into play. Basically this is because the message identintiy for message secuirty and the transport identity from IIS, both come into the picture and WCF doest know which one to pick and hence falls back to anonymous and then you get this exception.

 “Cannot start impersonation because the SecurityContext for the UltimateReceiver role from the request message with the ‘http://tempuri.org/<Service>/<Action>’ action is not mapped to a Windows identity.”

I was out the whole day trying to figure out how can i get this thing working. Finally [Wenlong] put up a full post “Impersonation with Double Identities”  that explained this whole scenario.

But if you just want to get your binding up and running with impersonation and message level security this should work with anonymous authentication.

<customBinding>

  <binding name=MyService>

    <security authenticationMode=SspiNegotiated>

    </security>

    <textMessageEncoding/>

    <httpTransport/>

  </binding>

</customBinding>

Pleaes note that you dont need to enable integrated windows authentication for this to work and SspiNegotiated will take care of this and your identity will flow through even if your Virtual directory is not setup with integrated windows authentication.