Zulfiqar's profileZulfiqar's SpaceBlogListsSkyDrive Tools Help

Zulfiqar's Space

WCF/WF/Oslo & random .Net stuff
2/10/2008

Moving to Dasblog

Spaces was a good experience but I think I need a richer blogging platform. So I'm moving to Dasblog.
http://www.zamd.net will be my new home.
 
 
 
1/8/2008

Per Endpoint UserName Authentication

WCF provides couple of different options for the validation of UserName & Password in case of UserName authentication. These options are:

  • Windows: Credentials are verified against a windows account (either local or domain).
  • Custom: Credentials are verified by calling your custom UserNamePasswordValidator component.
  • Membership: Credentials are verified by using SqlMembershipProvider.

You can specify the validation type using the following service behavior:

<serviceCredentials> <serviceCertificate findValue="01 0a 27" storeName="My" 509FindType="FindByThumbprint"/> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="UserNameValidationSamples"/> </serviceCredentials>

Using this behavior to set the validation results in same validation (custom in above case) to be used for all endpoints. Sometimes there is a requirement to support different type of UserName, Password validation for different endpoints.

By default, WCF doesn't provide this functionality. So I have created a custom endpoint behavior and a behavior extension to achieve the above mentioned functionality.

The implementation of the my custom behavior clones the ServiceCredentials behavior and override the userNamePasswordValidationMode with the endpoint's mode value.

namespace UserNameValidationSample { //behavior configuration extension element public class UserNameValidationBehaviorElement : BehaviorExtensionElement { public UserNameValidationBehaviorElement() { } public override Type BehaviorType { get { return typeof(UserNameValidationBehavior); } } protected override object CreateBehavior() { return new UserNameValidationBehavior(this.Mode); } [ConfigurationProperty("mode")] public UserNamePasswordValidationMode Mode { get { return (UserNamePasswordValidationMode)base["mode"]; } set { base["mode"] = value; } } ConfigurationPropertyCollection properties = null; protected override ConfigurationPropertyCollection Properties { get { if (this.properties == null) { ConfigurationPropertyCollection propertys = new ConfigurationPropertyCollection(); propertys.Add( new ConfigurationProperty("mode", typeof(UserNamePasswordValidationMode), null, ConfigurationPropertyOptions.IsRequired)); this.properties = propertys; } return this.properties; } } } public class UserNameValidationBehavior : IEndpointBehavior { UserNamePasswordValidationMode _mode; public UserNameValidationBehavior(UserNamePasswordValidationMode mode) { _mode = mode; } #region IEndpointBehavior Members public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { // override the validation mode for this endpoint. var sc = bindingParameters.Remove<ServiceCredentials>(); if (sc != null) { var scCopy = sc.Clone(); scCopy.UserNameAuthentication.UserNamePasswordValidationMode = _mode; bindingParameters.Add(scCopy); } } public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } #endregion } }

This is how you define and use a the behavior from config file:

<system.serviceModel> <services> <service name="UserNameValidationSamples.CalculatorService" behaviorConfiguration="credBehavior"> <endpoint address="http://localhost:8080/CalculatorService/Custom" binding="wsHttpBinding" bindingConfiguration="noSecureConversation" contract="UserNameValidationSamples.ICalculatorService" behaviorConfiguration="customValidation" /> <endpoint address="http://localhost:8080/CalculatorService/Windows" binding="wsHttpBinding" bindingConfiguration="noSecureConversation" contract="UserNameValidationSamples.ICalculatorService" behaviorConfiguration="windowsValidation" /> </service> </services> <behaviors> <endpointBehaviors> <!-- my custom behavior --> <behavior name="customValidation"> <userNameValidation mode="Custom"/> <!-- Custom, Windows, Membership--> </behavior> <behavior name="windowsValidation"> <userNameValidation mode="Windows"/> <!-- Custom, Windows, Membership--> </behavior> </endpointBehaviors> </behaviors> <!-- create a userNameValidation extension --> <extensions> <behaviorExtensions> <add name="userNameValidation" type="UserNameValidationSamples.UserNameValidationBehaviorElement"/> </behaviorExtensions> </extensions> </system.serviceModel>

 Click here to download complete source

11/18/2007

Security settings for this service require 'Anonymous' Authentication but it is not enabled for the IIS application that hosts this service.

[NotSupportedException: Security settings for this service require 'Anonymous' Authentication but it is not enabled for the IIS application that hosts this service.]

I have seen people struggling while configuring integrated windows authentication for their IIS hosted WCF services and are getting above exception. There are two settings required to make this work.

  • Enable Integrated Windows Authentication on IIS
  • In WCF configure the client credential to Windows

Sample WCF binding:

<bindings> <basicHttpBinding> <binding name="basicHttpBinding_IMyService"> <security mode="TransportCredentialOnly"> <transport clientCredentialType="Windows"/> </security> </binding> </basicHttpBinding> </bindings>

Even after making these two changes you might still be getting the exception and the prime reason is that one of the service endpoints (most likely the MEX endpoint) still requires anonymous access while it is disabled in IIS.

Why mostly MEX endpoint?

Because the default settings of mexHttpBinding allows anonymous access by setting clientCredentialsType to None. So if you have a mex endpoint and you are using out of box mexHttpBinding you will be getting above exception.

A simple fix is to use the same secured binding, in this case basicHttpBinding_IMyService, for the mex endpoint as well or create a new binding and disable the anonymous access for mex endpoint as well.

11/12/2007

Message security explained

<message clientCredentialType="UserName" establishSecurityContext="false" negotiateServiceCredential="false" algorithmSuite="Default"/>

Impact/Functionality of various attributes

  • establishSecurityContext

Setting establishSecurityContext=false results in one shot message security. In this mode every message is secured indepedently -- algorithm suits determines what algorithm should be used to secure the message. By default WCF uses RSA-OAEP, which uses a symmetric key to encrypt the message and signature. This symmetric key is then wrapped/transported using the public key from the server certificate. All of this security stuff is contained in every SOAP message.

With one shot message security there are further two options:

  • Not using derived keys

Above explanation was for case in which derived keys were turned off. Some of the other stacks (e.g IBM DataPower) doesn’t support derived keys yet so in those cases this needs to turned off. By default wsHttpBinding uses the derived keys and there is no way to turn this off from the config file. You have to use a custom binding, if you need to turn off derived keys. BasicHttpBinding, by default, turns off dervied keys.

  • Using derived keys

With derived keys turned on, WCF derives two keys from the symmetric key and use one to sign the SOAP message and other to encrypt various parts of the SOAP message. It is considered bad practice to sign and encrypt data using the same key as certain attacks are more likely to succeed in this case so derived keys provide more resilient against such attacks and that’s why its enabled by wsHttpBinding as a default option.

Setting establishSecurityContext=true results in a Secure Context Token established between client and server. One this token is created on both ends – all subsequent message exchanges will be secured using this context token. By default this token is issued with a 15 minute life time and need to be re-issued if required beyond 15 minutes.

Again not using derived keys here means multiple message (exchanged in 15 minutes life-span) will be secured using the same Security Context token so security in this case will be more weaker than one shot security. So u

se of derived keys is more important in case of secure conversation.

 

<customBinding> <binding name="Binding1"> <security authenticationMode="SecureConversation" requireDerivedKeys="false"> <secureConversationBootstrap authenticationMode="UserNameForCertificate" requireDerivedKeys="false"/> </security> <textMessageEncoding/> <httpTransport/> </binding> </customBinding>

  • negotiateServiceCredentials

Credential negotiation is a process of exchanging  keys and authenticating communicating parties using some form of handshake. Now credential negotiate can be done at the transport level using something like SSL over HTTPS or can be done at SOAP level using TLSNego (SSL over SOAP).

Setting negotiateServiceCredentials=false means that service credentials should be made available to the client using some out-of-band mechanism. Now in WCF service can have different types of credentials:

  • Certificate
  • Kerberos token

In case of Certificate credentials, the service certificate should be provided to all the clients using some out-of-band mechansim and client should refer the the service certificate in their security configuration.

<clientCredentials> <serviceCertificate> <defaultCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/> <authentication certificateValidationMode="PeerOrChainTrust" /> </serviceCertificate> </clientCredentials>

In the above config, defaultCertificate element refers to the service certificate passed to this client.

In case of Kerberos credentials, setting negotiateServiceCredentials=false results in on-shot kerberos or sometimes called kerberos direct. For this mode to work service must be running under machine SPN (NetworkService, LocalService accounts etc) and client must specify the SPN in the endpoint identity.

<endpoint address="http://localhost/servicemodelsamples/service.svc" binding="customBinding" bindingConfiguration="Binding1" behaviorConfiguration="ClientCredentialsBehavior" contract="Microsoft.ServiceModel.Samples.ICalculator" > <identity> <servicePrincipalName value="host/zamd.local"/> </identity> </endpoint>

Setting this attribute to true will results in credentials negotiation. Now as mentioned earlier actual negotiation depends on client and service credential type.

<wsHttpBinding> <binding name="Binding1"> <security mode="Message"> <message clientCredentialType="UserName"/> </security> </binding> </wsHttpBinding>

The above binding will result in UserNameForSslNegotiated credential type -- which uses UserName as client credential type and certificate as service credential and service credentials will be negotiated using SSL over SOAP(TLSNEGO)

10/8/2007

How to set ProtectionLevel from config

Protectionlevel enum defines the level of security in terms of encryption & signature. When programing at the Service Model layer, ProtectionLevel can be set at various levels:

  • ServiceContract level: all messages/faults of the service contract will have same protection level
  • OperationContract level: An operation can override the protection level specified at the contract level. So messages/faults of an operation can have different protection requirements.
  • MessageContract level: Individual messages/faults can also specify their own protection level, overriding the one specified at higher level.
[ServiceContract(Namespace = "http://www.zamd.net/Calcualtor", ProtectionLevel = ProtectionLevel.Sign)] public interface ICalculator { [OperationContract] int Add(int a, int b); }

all good so far, however there are couple of limitation with the current implementation.

  1. ProtectionLevel can only be set in the code. Its quite common requirement that operations/IT want to control the protection level as part of the deployment. Hard coding protection level in code as an attribute is quite in-efficient in that case. As you have to recompile to change the protection level.
  2. Another more subtle requirement I have seen is to have the ability to have different Protectionlevel for different endpoints sharing the same contract.  e.g  an intranet endpoint and an internet endpoint sharing the same contract. You might want to only sign the messages for intranet communication while both singing and encrypting for internet communication. Currently because Protectionlevel is specified as part of the contract, which gets shared by multiple endpoints. There is no built-in way to achieve this other than creating multiple contracts.

If your only requirement is to be able to set protection level from config file, then a simple option could be to access the description object model and update the contract protection level there before opening the service host or proxy object.

ServiceHost svc = new ServiceHost(typeof(CalculatorService)); svc.Description.Endpoints[0].Contract.ProtectionLevel = GetProtectionLevelFromConfig();

If however, you want to set different protection level for different endpoints, then you have to do some more work.

Specifically you have to create an endpoint behavior, from which you access ChannelProtectionRequirements parameter to specify endpoint specific protection requirements for that endpoint. Here is step by step procedure to do this:

 

1. Create an endpoint behavior and add following code to AddBindingParameters method.

public class ProtectionLevelBehavior : IEndpointBehavior { ProtectionLevel level; internal ProtectionLevelBehavior(ProtectionLevel level) { this.level = level; } #region IEndpointBehavior Members public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { var proReq = bindingParameters.Remove<ChannelProtectionRequirements>(); proReq = new ChannelProtectionRequirements(); MessagePartSpecification unProtectedSpec = new MessagePartSpecification(); MessagePartSpecification protectedSpec = new MessagePartSpecification(true); //I'm setting same protection level for all the actions. // You could specify different protection level per action, if required. // Also note, I haven't implemented any support for custom SOAP headers. // However that can easily be added using the same mechansim. switch (level) { case ProtectionLevel.None: proReq.OutgoingSignatureParts.AddParts(unProtectedSpec, "*"); proReq.IncomingSignatureParts.AddParts(unProtectedSpec, "*"); proReq.OutgoingEncryptionParts.AddParts(unProtectedSpec, "*"); proReq.IncomingEncryptionParts.AddParts(unProtectedSpec, "*"); break; case ProtectionLevel.Sign: proReq.OutgoingSignatureParts.AddParts(protectedSpec, "*"); proReq.IncomingSignatureParts.AddParts(protectedSpec, "*"); proReq.OutgoingEncryptionParts.AddParts(unProtectedSpec, "*"); proReq.IncomingEncryptionParts.AddParts(unProtectedSpec, "*"); break; case ProtectionLevel.EncryptAndSign: proReq.OutgoingSignatureParts.AddParts(protectedSpec, "*"); proReq.IncomingSignatureParts.AddParts(protectedSpec, "*"); proReq.OutgoingEncryptionParts.AddParts(protectedSpec, "*"); proReq.IncomingEncryptionParts.AddParts(protectedSpec, "*"); break; } // Add our protection requirement for this endpoint into the binding params. bindingParameters.Add(proReq); } public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } #endregion }

2. Now create a behavior extension so that this behavior can be enabled/disabled using configuration file. The behavior extension will read the configured protectionLevel from config file and will create an instance of above endpoint behavior to plug it into behavior collections.

//behavior configuration extension element public class ProtectionLevelBehaviorElement : BehaviorExtensionElement { public ProtectionLevelBehaviorElement() { } public override Type BehaviorType { get { return typeof(ProtectionLevelBehavior); } } protected override object CreateBehavior() { return new ProtectionLevelBehavior(this.Level); } [ConfigurationProperty("level")] public ProtectionLevel Level { get { return (ProtectionLevel)base["level"]; } set { base["level"] = value; } } ConfigurationPropertyCollection properties = null; protected override ConfigurationPropertyCollection Properties { get { if (this.properties == null) { ConfigurationPropertyCollection propertys = new ConfigurationPropertyCollection(); propertys.Add(new ConfigurationProperty("level", typeof(ProtectionLevel), null, ConfigurationPropertyOptions.IsRequired)); this.properties = propertys; } return this.properties; } } }

3. And this is how you will specify the protection level from config file using the above behavior extension.

<behaviors> <endpointBehaviors> <behavior name="endpointSecurityConfig"> <protectionLevel level="Sign"/> <!-- None, Sign, EncryptAndSign--> </behavior> </endpointBehaviors> </behaviors>

Complete Source

enjoy :)

 
Public folders