WCF and WS-Security Updated
WCF and WS-Security Updated
Author
Chris Seary is a security specialist and MVP at IT and management consultancy,
Charteris plc. He has worked with many different Microsoft technologies, and for the last
few years has specialised in the security aspects of enterprise-level .NET systems.
Technology
.Net Framework 3.0
Visual Studio 2.0
Summary
Windows Communication Foundation is the new Microsoft technology for
communicating between distributed services. This article will focus on communication
protected by WS-Security.
Introduction - What is WS-Security?
Microsoft, IBM and a number of other vendors and organisations have created standards
for protection of communications at the message level. These standards cover many
aspects of security, including digital signatures, authentication and encryption of SOAP
messages. The generic name for the standards is WS-*, and includes WS-Security, WSTrust and WS-SecureConversation.
Windows Communication Foundation
There are three aspects of a service created with the Windows Communication
Foundation - Address, Binding and Contract. Address is the URI of the service, Binding
is the method of communication, and contract is the definition of the service methods.
Creating the service
The physical implementation of a contract is an interface. Heres the interface for our
service, called IMyService:
[ServiceContract]
public interface IMyService
{
[OperationContract]
string GetData(string val);
}
The interface has one method, which is GetData (string val). The ServiceContract
attribute is added to the interface definition, and the OperationContract attribute is added
to the method.
Our service will implement this interface:
namespace MyServiceHost
{
[ServiceContract]
public interface IMyService
{
[OperationContract]
string GetData(string val);
}
public class MyService : IMyService
{
string IMyService.GetData(string val)
{
string s = System.ServiceModel.ServiceSecurityContext
.Current.PrimaryIdentity.Name;
return "You sent the text:" + val +
Environment.NewLine + s;
}
}
}
This is a very simple service, which simply returns the parameter passed and the identity
of the user who is authenticated with the credentials supplied.
This service is hosted in a Windows Forms application. A reference to
System.ServiceModel is required.
The Load event instantiates the service:
private void ServiceForm_Load(object sender, EventArgs e)
{
try
{
sh = new System.ServiceModel.ServiceHost(typeof(MyService));
sh.Open();
}
</services>
<bindings>
<wsHttpBinding>
<binding name="serviceConfig">
<security mode = "Message" >
<message
clientCredentialType="UserName"
negotiateServiceCredential ="False"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehave" >
<serviceCredentials>
<serviceCertificate
x509FindType="FindBySubjectName"
storeLocation="LocalMachine"
storeName="My"
findValue="localhost" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Weve got rid of the metadata endpoint here, as the proxy has already been generated.
First, lets look at the endpoint element:
<endpoint
address ="http://localhost:9091/DataService"
binding ="wsHttpBinding"
contract="MyServiceHost.IMyService"
bindingConfiguration ="serviceConfig"
/>
This endpoint has a bindingConfiguration attribute called serviceConfig. Lets look at
this configuration:
<bindings>
<wsHttpBinding>
<binding name="serviceConfig">
<security mode = "Message" >
<message
clientCredentialType="UserName"
negotiateServiceCredential ="False"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
Here, we can see that the security mode is set to Message. This means that security is
being applied to the SOAP. Username and password is added to the XML passed to the
web service, rather than via the HTTP variables.
In the <message> element, the clientCredentialType is set to UserName, which means
that the username and password are passed in the SOAP the alternative could be
Windows, X509 certificate, None or a custom issued token. The
negotiateServiceCredential is set to false, as were not obtaining a credential through
negotiation (as we would if we were using WS-SecureConversation).
Now, its not advisable to pass a username and password in the clear, as this could be
picked up by a network monitoring tool such as Ethereal. Dont forget, were not relying
on network security here (such as HTTPS or IPSec). Its necessary to encrypt the
credentials, and for this a certificate is used. Here is the configuration information that is
referred to in the <service> element:
<behavior name="serviceBehave" >
<serviceCredentials>
<serviceCertificate
x509FindType="FindBySubjectName"
storeLocation="LocalMachine"
storeName="My"
findValue="localhost" />
</serviceCredentials>
</behavior>
This specifies that a certificate from the local machine store with the subject name
localhost is to be used. The public key from the certificate will be used to encrypt, and
only the owner of the private key will be able to read the username and password.
Now we need to configure the client to speak to this newly secured service.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint
address ="http://localhost:9091/DataService"
binding ="wsHttpBinding"
contract="IMyService"
bindingConfiguration ="clientConfig"
/>
</client>
<bindings>
<wsHttpBinding>
<binding name="clientConfig">
<security mode="Message">
<message
clientCredentialType="UserName"
negotiateServiceCredential="False"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
All that weve added is a bindingConfiguration that specifies the security mode and what
sort of credentials are passed. In the client code, we can specify the certificate and
credentials passed. Heres the code from the button click event that calls the service:
private void btnGetData_Click(object sender, EventArgs e)
{
try
{
MyServiceClient msc = new MyServiceClient();
msc.ClientCredentials.UserName.UserName = @"wseuser";
msc.ClientCredentials.UserName.Password = "Spotlight1";
msc.ClientCredentials.ServiceCertificate.SetDefaultCertificate(
System.Security.Cryptography.X509Certificates
.StoreLocation.CurrentUser,
System.Security.Cryptography.X509Certificates.StoreName.My,
System.Security.Cryptography.X509Certificates.X509FindType
.FindBySubjectName,
"localhost");
string s = msc.GetData("stuff");
txtRes.Text = s;
}
catch (Exception ex)
{
txtRes.Text = ex.ToString();
}
}
First, the client credentials are set:
msc.ClientCredentials.UserName.UserName = @"wseuser";
msc.ClientCredentials.UserName.Password = "Spotlight1";
Then the certificate is chosen from the Current User store by calling the
SetDefaultCertificate method of the proxy:
msc.ClientCredentials.ServiceCertificate.SetDefaultCertificate(
System.Security.Cryptography.X509Certificates
.StoreLocation.CurrentUser,
System.Security.Cryptography.X509Certificates.StoreName.My,
System.Security.Cryptography.X509Certificates.X509FindType
.FindBySubjectName,
"localhost");
The public key from the certificate is used by the client to encrypt the credentials. The
credentials are authenticated by Windows when they reach the service.
Its possible to use other methods of verifying these credentials for instance, a custom
uername token manager can be written which plugs into ASP.Net Membership
functionality. This is one of the ways where WS-Security is really useful youre not
restricted to using Windows LDAP directories for storing security principals.
Logging Messages
To log the messages produced by the WCF service, we add a system.diagnostics section
to the App.Config:
<configuration>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" >
<listeners>
<add name="messages"
type ="System.Diagnostics.XmlWriterTraceListener"
initializeData="C:\WCFTest\ServiceMessages.xml" />
</listeners>
</source>
</sources>
</system.diagnostics>
.
.
</configuration>
This sets up a listener to write out the log entries that are generated. At the end of the
config file, we configure the listener:
<configuration>
.
.
<diagnostics>
<messageLogging
logEntireMessage="true"
logMalformedMessages="false"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="false"
maxMessagesToLog="3000"
maxSizeOfMessageToLog="4000" />
</diagnostics>
</system.serviceModel>
</configuration>
The service is now logging the entire message at the service level.
How do we examine the log file? The Windows SDK provides a tool called the Service
Trace Viewer, which can be used to examine the log. This tool allows a user to view and
filter log entries.
10
Summary
Windows Communication Foundation provides a solid, configurable framework for
creating distributed services. WS-Security is a rapidly evolving security interface which
allows a common mechanism for securing communications.
The wsHttpBinding allows integration of WS-Security within WCF, giving a security
model which is both platform independent and extensible.
11