In the previous post, we have stated the several problems not being able to configure ProtectionLevel
for different endpoint declaratively using configuration files. In this post, we address this issue and thereby, describe how to specify ProtectionLevel
for multiple endpoints of a service using .config
file.
Outline. In this post, we start by stating motivation of specifing ProtectionLevel
for multiple endpoints of a service declaritvely instead of programmatically. Then, we discuss the steps to achieve it.
Motivation: ProtectionLevel for Multiple Endpoints
Basically, ProtectionLevel
enforces a security requirement on request and response messages in the channel, and all the consumer of the message must conform to that requirement; anything otherwise results in runtime exception. In the last post, we have described how to configure ProtectionLevel
at different level of WCF messaging stack and observed that it can only be set programmatically in the contract of a WCF service, which unfortunately has impacts on all the preconfigured bindings.
For instance, consider that we would like to have two different endpoints to use different ProtectionLevel
. In addition, we want to make it configurable so that we can the security behavior of the endpoint conveniently after it has been deployed.
Are these requirements practical? To answer that, consider following case: we have only one endpoint and we are using message level security with wsHttpBinding
(or ws2007httpbinding
preferable one for internet based WCF Service). For internet users consuming this service, we are using ProtectionLevel.EncryptAndSign
because of the security requirement imposed for our application. However, in case of local intranet, we don’t want to take the overhead of
ProtectionLevel.EncryptAndSign
rather would like to use ProtectionLevel.Sign
to make the service a bit more responsive and efficient by getting rid of the overhead of encryption of requests and responses. Most importantly, we don’t need ProtectionLevel.EncryptAndSign
for the messages in this context as per security policy. Out of the box, there is no features available that can enable the use of different ProtectionLevel
in these cases.
Obviously, there is one naive approach to host to service twice by compiling the code in 2 different ProtectionLevel
. At 1 to 10 scale, how would you rate this solution ? Ok , then let’s move on…
Thus, what we need is to make two different endpoints to work with different ProtectionLevel
, and using custom endpoint behavior, it can be achieved. Thus, the internet users will be able to use the wsHttpBinding
with default ProtectionLevel
, while the intranet users use the less secure– ProtectionLevel.Sign
. The next section shows how to achieve this.
ProtectionLevel Configuration via Custom Endpoint Behavior
To do this, we have following these steps.First we have to create a Custom EndpointBehavior by implementing IEndpointBehavior
as below –
public class MessageSecurityBehavior:IEndpointBehavior | |
{ | |
public ProtectionLevel ProtectionLevel { get; set; } | |
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) | |
{ | |
//Do nothing | |
} | |
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) | |
{ | |
//Do nothing | |
} | |
public void Validate(ServiceEndpoint endpoint) | |
{ | |
//Do nothing | |
} | |
public void AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection parameters) | |
{ | |
//Setting the ProtectionLevel at the Service Contract | |
serviceEndpoint.Contract.ProtectionLevel = ProtectionLevel; | |
parameters.Remove<channelprotectionrequirements>(); | |
ChannelProtectionRequirements requirements = new ChannelProtectionRequirements(); | |
parameters.Add(requirements); | |
MessagePartSpecification unprotectedBody = new MessagePartSpecification(); | |
MessagePartSpecification protectedBody = new MessagePartSpecification(true); | |
switch (ProtectionLevel) | |
{ | |
case ProtectionLevel.None: | |
requirements.OutgoingSignatureParts.AddParts(unprotectedBody, "*"); | |
requirements.IncomingSignatureParts.AddParts(unprotectedBody, "*"); | |
requirements.OutgoingEncryptionParts.AddParts(unprotectedBody, "*"); | |
requirements.IncomingEncryptionParts.AddParts(unprotectedBody, "*"); | |
break; | |
case ProtectionLevel.Sign: | |
requirements.OutgoingSignatureParts.AddParts(protectedBody, "*"); | |
requirements.IncomingSignatureParts.AddParts(protectedBody, "*"); | |
requirements.OutgoingEncryptionParts.AddParts(unprotectedBody, "*"); | |
requirements.IncomingEncryptionParts.AddParts(unprotectedBody, "*"); | |
break; | |
case ProtectionLevel.EncryptAndSign: | |
requirements.OutgoingSignatureParts.AddParts(protectedBody, "*"); | |
requirements.IncomingSignatureParts.AddParts(protectedBody, "*"); | |
requirements.OutgoingEncryptionParts.AddParts(protectedBody, "*"); | |
requirements.IncomingEncryptionParts.AddParts(protectedBody, "*"); | |
break; | |
} | |
} | |
} |
Then, we create a BehaviorElement
by extending BehaviorExtensionElement
to make the behavior configurable through config file.
/// <summary> | |
/// Represents Element to set the ProtectionLevel to different Endpoint using configuration | |
/// </summary> | |
public class MessageSecurityBehaviorElement : BehaviorExtensionElement | |
{ | |
private const string PROTECTION_LEVEL_ELEMENT_NAME = "messageProtection"; | |
public override Type BehaviorType | |
{ | |
get | |
{ | |
return typeof(MessageSecurityBehavior); | |
} | |
} | |
protected override object CreateBehavior() | |
{ | |
return new MessageSecurityBehavior { ProtectionLevel = this.ProtectionLevel }; | |
} | |
[ConfigurationProperty(PROTECTION_LEVEL_ELEMENT_NAME)] | |
public ProtectionLevel ProtectionLevel | |
{ | |
get | |
{ | |
return (ProtectionLevel)base[PROTECTION_LEVEL_ELEMENT_NAME]; | |
} | |
set | |
{ | |
base[PROTECTION_LEVEL_ELEMENT_NAME] = value; | |
} | |
} | |
private ConfigurationPropertyCollection properties = null; | |
protected override ConfigurationPropertyCollection Properties | |
{ | |
get | |
{ | |
if (this.properties == null) | |
{ | |
ConfigurationPropertyCollection propertys = new ConfigurationPropertyCollection(); | |
propertys.Add(new ConfigurationProperty(PROTECTION_LEVEL_ELEMENT_NAME, typeof(ProtectionLevel), null, ConfigurationPropertyOptions.IsRequired)); | |
properties = propertys; | |
} | |
return properties; | |
} | |
} | |
} |
Thus the coding part this done. Let’s start configuring. To do so, first thing that needs to be done is to add a behaviorExtensions
inside system.serviceModel>behaviorExtensions
specifying the newly created custom Endpoint behavior :
<system.servicemodel> | |
<extensions> | |
<behaviorExtensions> | |
<add name="messageProtection" type="TestService. MessageSecurityBehaviorElement, TestService"/> | |
</behaviorExtensions> | |
</extensions> | |
<!--Rest of the configuration--> | |
</system.servicemodel> | |
Then, we create a endpoint behavior like below:
<system.servicemodel> | |
<behaviors> | |
<endpointBehaviors> | |
<behavior name="noneProtectionLevelForEndPoint"> | |
<messageProtection protectionLevel="None"/> | |
</behavior> | |
</endpointBehaviors> | |
</behaviors> | |
<!--Rest of the configuration--> | |
</system.servicemodel> |
Now, if we need to use a similar ProtectionLevel
that we configured at the previous step in any endpoint , we simply need to add it as behaviorConfiguration
, and we are done.
<system.servicemodel> | |
<!-- Rest of the configuration--> | |
<endpoint address="wsHttp" | |
binding="wsHttpBinding" | |
name="wsBinding.ModeMessage.CredentialNone.BindingName" | |
contract="TestService.ITestService" | |
behaviorConfiguration="noneProtectionLevelForEndPoint"> | |
<identity> | |
<dns value="localhost"/> | |
</identity> | |
</endpoint> | |
</system.servicemodel> |
More on MessageSecurityBehavior
So far, we have described how to configure ProtectionLevel
at runtime. Next we explain MessageSecurityBehavior
. By changing the ChannelProtectionRequirement
of an Endpoint, the new custom behavior impacts requests and responses of the channel. Moreover, the contract also binds to the configured ProtectionLevel
. Then, the two different MessagePartSpecification
was created, where 1st one is an empty MessagePartSpecification
, and 2nd one refers to the MessagePartSpecification
which contains body.
Depending of different value ProtectionLevel
, the MessagePartSpecification
are set to ChannelProtectionRequirements
.
For instance, in case ProtectionLevel.Sign
, in OutgoingSignatureParts
and IncomingSignatureParts
of ChannelProtectionRequirements
MessagePartSpecification
that included Body is being added to be signed from client to the server and again back to client from server. However, in this case , encryption is not needed, so in OutgoingEncryptionParts
and IncomingEncryptionParts
, empty MessagePartSpecification
is added, and that results in unencrypted messages.
Conclusion
In this post, we show how to declaritively specify the ProtectionLevel
for multiple endpoints exposed by a WCF service. Though it’s not difficult to update the ProtectionLevel
at runtime, we must note that client and server always conform to the ProtectionLevel
requirement, and as a consequence, updating the ProtectionLevel
at runtime might results in updating the clients configuration/code.
Additional Links
- WCF Security: WCF Performance & ProtectionLevel – Part 1 : https://adilakhter.wordpress.com/2009/08/06/wcf-security-wcf-performance-protectionlevel-part-1/
- Custom WCF Behaviors through App.Config :http://winterdom.com/2006/10/customwcfbehaviorsthroughappconfig
- Configuring ProtectionLevel : http://blogs.msdn.com/drnick/archive/2008/03/10/configuring-protection-level.aspx
- Fundamentals of WCF Security : http://www.code-magazine.com/article.aspx?quickid=0611051
I have written our own binding which is derived from WsFederationHttpBinding. Is there a way for us to turn off the encryption and signing in our binding? Thanks.
By default, the value of the ProtectionLevel for WSFederationHttpBinding is EncryptAndSign. Although, I haven’t tried changing the ProtectionLevel with WSFederationHttpBinding Binding , (by design of WCF) I think it should be possible to change the ProtectionLevel using ProtectionLevel Attribute or a custom IEndpointBehavior (unless I am missing something).
At what level of WCF Message is this being applied? Can this be changed to be at any level?
Hi,
Great post! I’ve always thought that it was a mistake to couple the protection level to the contract, this setting should be configurable at the endpoint level, as you’ve done with your behavior.
The only catch is that the wsdl will not reflect the “overwritten” protection level, am I right?