Windows Authorization in WCF with PrincipalPermission

In the previous example we did an impersonation of a windows user on a service operation. With the PrincipalPermission attribute we can also provide role based security. To test this on the WriteToLogFile operation of the EchoService, we create a local group named EchoLogWriters. In this group the user WcfTester is placed.

EchoLogWriters Group Properties

On the service operation we declare the PrincipalPermission as follows.

[PrincipalPermission(SecurityAction.Demand, Role = "l040\\EchoLogWriters")]
public void WriteToLogFile(string logText)
{
    const string fileName = @"C:\logdir\logfile.txt";
    StreamWriter streamWriter = null;

    try
    {
        streamWriter = File.Exists(fileName)
                            ? File.AppendText(fileName)
                            : new StreamWriter(fileName);

        streamWriter.WriteLine("{2} - {0}: {1}",
            Thread.CurrentPrincipal.Identity.Name,
            logText,
            DateTime.Now.ToLongTimeString());
    }
    catch (Exception)
    {
        throw new FaultException("Log Access Denied");
    }
    finally
    {
        if (streamWriter != null)
            streamWriter.Close();
    }
}

Now any user who is a member of the EchoLogWriters group can call the WriteToLogFile operation.

How to Impersonate a Windows Client on a WCF Service

The previous example showed how to configure message based security with Windows authentication. So we know who the user is calling our service operations. But what can the user do on our system? On windows systems we can use user impersonation for authorization. We simply service the operation call as the calling user, instead of the logged in user account. In the following example the authenticated Windows user call the operation WriteToLogFile of the EchoService. We impersonate the current logged in account on the service side with the calling user. We dos this by setting the OperationBehaviour Impersonation property to ImpersonationOption Required If the user has no write rights to the file a FaultException is thrown.

[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public void WriteToLogFile(string logText)
{
    const string fileName = @"C:\logdir\logfile.txt";
    StreamWriter streamWriter = null;

    try
    {
        streamWriter = File.Exists(fileName)
                            ? File.AppendText(fileName)
                            : new StreamWriter(fileName);

        streamWriter.WriteLine("{2} - {0}: {1}",
            Thread.CurrentPrincipal.Identity.Name,
            logText,
            DateTime.Now.ToLongTimeString());
    }
    catch (Exception)
    {
        throw new FaultException("Log Access Denied");
    }
    finally
    {
        if (streamWriter != null)
            streamWriter.Close();
    }
}

On the client side, don’t forget to set the AllowedImpersonationLevel to TokenImpersonationLevel “Impersonation” or else the impersonation won’t fly. This level gives the service the right to impersonate the client locally, but not on a remote system.

using System;
using System.Net;
using System.Security.Principal;
using EchoClientConsole.EchoServiceReference;

namespace EchoClientConsole
{
    internal class Program
    {
        private static void Main()
        {
            var channel = new EchoServiceClient(
                "NetTcpBinding_IEchoService"
                );

            channel.ClientCredentials.Windows.AllowedImpersonationLevel =
                TokenImpersonationLevel.Impersonation;

            var networkCredential =
                new NetworkCredential
                    {
                        UserName = @"l040\WcfTester",
                        Password = "WcfTestPassword"
                    };

            channel.ClientCredentials.Windows
                .ClientCredential = networkCredential;

            try
            {
                channel.WriteToLogFile("Can I Log this?");
                Console.ReadLine();
                channel.Close();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.Message);
                Console.ReadLine();
                channel.Abort();
            }
        }
    }
}

Now we can provide access to the logfile by administration of the windows ACL for a specific user.

Setting Permissions for logdir

WCF Message Security with Windows Credentials on NetTcpBinding

In a previous example I configured the EchoService to use transport security for BasicHttpBinding. Only the transport pipe is secured in this case. If point-to-point security isn’t secure enough, we can use end-to-end message based security. The following example configures a NetTcpBinding endpoint to use message based security with Windows Authentication.

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <bindings>
      <netTcpBinding>
        <binding name="MessageWindowsCredentialsNetTcpBinding">
          <security mode="Message">
            <message clientCredentialType="Windows"/>
          </security>
        </binding>
      </netTcpBinding>
    </bindings>
    <services>
      <service name="WcfServiceLibrary.Echo.EchoService">
        <endpoint 
          address="net.tcp://localhost:8081/EchoService" 
          binding="netTcpBinding"
          bindingConfiguration="MessageWindowsCredentialsNetTcpBinding"
          contract="WcfServiceLibrary.Echo.IEchoService" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

On the client side we need to provide windows credentials to the service before invoking operations. Since we didn’t specify the message ProtectionLevel, all messages are now signed and encrypted by default.

using System.Net;
using EchoClientConsole.EchoServiceReference;

namespace EchoClientConsole
{
    internal class Program
    {
        private static void Main()
        {
            var channel = new EchoServiceClient(
                "NetTcpBinding_IEchoService"
                );

            var networkCredential =
                new NetworkCredential
                    {
                        UserName = "lbokhorst",
                        Password = "*********"
                    };

            channel.ClientCredentials.Windows
                .ClientCredential = networkCredential;

            EchoMessage reply = channel.Echo(
                new EchoMessage
                    {
                        Created = DateTime.Now,
                        Text = "Windows Credentials "
                    });

            Console.WriteLine(reply.Text);
            Console.ReadLine();
            channel.Close();
        }
    }
}

The next example handles impersonation of a Windows Client on a WCF Service.

Using SSL Transport Security with WCF BasicHttpBinding

If you create a BasicHttpBinding endpoint in WCF, by default neither messages or transportation is secure. Anyone snooping on the wire can read along. An easy way to secure communication over HTTP is by using Secure Sockets Layer (SSL). This provides point-to-point security, giving it a secure pipe at the transport layer.

If you want to use a BasicHttpBinding over HTTPS, you need a SSL Certificate signed by a certification authority. For development and testing purposes you can provide one yourself. First create and install a temporary certificate on your local machine. If you don’t know how, be sure to read this tutorial first. After that, configuring your service is a breeze.

Shown here is the app.config of the EchoService. A custom BasicHttpBinding was created with security mode “Transport”. An endpoint is created from that custom binding to the SSL port with our certificate bound to it. The identity section of the service has a certificate reference to the thumbprint of the certificate on our local machine.

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicSecure">
          <security mode="Transport" />
        </binding>
      </basicHttpBinding>
    </bindings>
    <services>
      <service name="WcfServiceLibrary.Echo.EchoService">
        <endpoint 
          address="https://localhost:8888/EchoService/" 
          binding="basicHttpBinding"
          bindingConfiguration="BasicSecure" 
          contract="WcfServiceLibrary.Echo.IEchoService">
          <identity>
            <certificateReference 
              storeName="My" 
              storeLocation="LocalMachine"
              x509FindType="FindByThumbprint" 
              findValue="f1b47a5781837112b4848e61de340e4270b8ca06" />
          </identity>
        </endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/" />
          </baseAddresses>
        </host>
      </service>
    </services>
<behaviors>
  <serviceBehaviors>
    <behavior name="">
      <serviceMetadata httpGetEnabled="true"/>
    </behavior>
  </serviceBehaviors>
</behaviors>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
  </startup>
</configuration>

Settings for the certificate for the service in the Service Configuration Editor below.

Wcf Configuration Tool Identity Certificate

On the client side, since we do not ask for any client credentials, a service reference update generates the proper client configuration in app.config. The communication of messages between the client and the service is now secured at the transport level with SSL.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding 
                  name="BasicHttpBinding_IEchoService" closeTimeout="00:01:00"
                  openTimeout="00:01:00" receiveTimeout="00:10:00" 
                  sendTimeout="00:01:00" allowCookies="false" 
                  bypassProxyOnLocal="false" 
                  hostNameComparisonMode="StrongWildcard"
                  maxBufferSize="65536" maxBufferPoolSize="524288" 
                  maxReceivedMessageSize="65536"
                  messageEncoding="Text" textEncoding="utf-8" 
                  transferMode="Buffered" useDefaultWebProxy="true">
                    <readerQuotas 
                      maxDepth="32" maxStringContentLength="8192" 
                      maxArrayLength="16384" maxBytesPerRead="4096" 
                      maxNameTableCharCount="16384" />
                    <security mode="Transport">
                        <transport 
                          clientCredentialType="None" 
                          proxyCredentialType="None"
                           realm="" />
                        <message 
                          clientCredentialType="UserName" 
                          algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint 
              address="https://localhost:8888/EchoService/" 
              binding="basicHttpBinding"
              bindingConfiguration="BasicHttpBinding_IEchoService" 
              contract="EchoServiceReference.IEchoService"
              name="BasicHttpBinding_IEchoService" />
        </client>
    </system.serviceModel>
</configuration>

How To Install Test Certificate for WCF SSL Transport

It took me some time to figure this out. For a secure message transport we need a Secure Sockets Layer (SSL) Certificate from a Trusted Root Certification Authority like Symantec VeriSign. For development and testing purposes of WCF services you can be your own Certification Authority and issue a valid local certificate yourself.

Step 1
First create a root authority certificate and local private key. Open the Visual Studio command prompt, CD to the path where you want to put the certificate file. Then use the MakeCert.exe tool as follows:

makecert -n "CN=RemondoCA" -r -sv RemondoCA.pvk RemondoCA.cer

Step 2
Open the Microsoft Management Console (MMC). Add and/or open the “Trusted Root Certification Authorities” node and right-click “Import” on the “Certificates” node to install the root authority certificate we just made.

Certificates MMC

Step 3
Install a certificate for this local machine issued and signed by our local authority by using MakeCert from the command line:

makecert -iv RemondoCA.pvk -n "CN=localhost" -ic RemondoCA.cer localhost.cer -sr LocalMachine -ss My -sky exchange

Step 4
Get the Thumbprint of the certificate we just installed. Open MMC again and find the “Personal/Certificates” node.

Personal Certificates MMC

Double click the certificate and scroll to the Thumbprint property. Copy the value into a text document and remove all the spaces. Copy the value to the clipboard.

Certificate Details

Step 5
Bind the certificate to a port on the local system. Use netsh on the command line. select the port you want to use for your service (in this case 8888). The certhash is you certificate thumbprint (without the spaces). The appid can be any valid Guid:

netsh http add sslcert ipport=0.0.0.0:8888 certhash=e0e719dadcb5af84ba78d3e435643ac914d6e3ff appid={00112233-4455-6677-8899-AABBCCDDEEFF}

Step 6
Run netsh once more to show an overview of the registered ports:

netsh http show sslcert

Netsh Certificates Console

And that’s all. In a future post I will be testing a WCF service with BasicHttpBinding and SSL transport. That’s why I had to install this certificate in the first place ;-)

CIA Triad and the Fundamentals of Computer Security

CIA TriadJust a pointer to dust of some of the key principles of system security. The CIA Triad is an industry standard security model developed to help us think about important aspects of the security of our services. CIA stands for Confidentiality, Integrity and Availability; three key principles which should be well implemented in any kind of secure system.

The short presentation below gives an overview of the various aspects of computer system security.

Configure message ProtectionLevel in WCF

On to client and service security in WCF. You can set the protection level of messages sent over the wire at the message, fault, operation and/or service level in WCF. There are three message ProtectionLevel property flavors to set as an attribute. This ensures that any endpoint used for the service will require this protection level as a minimum.

  • None
    Plain text traveling over the wire.
  • Sign
    The message is digitally signed.
    Ensures no modification to the message.
    The message is still plain text.
  • EncryptAndSign
    Before signing, the message is encrypted.
    Ensures no modification to the message and is scrambled.

If, for instance, the EchoService is set a ProtectionLevel of EncryptAndSign on the ServiceContract level, an endpoint with BasicHttpBinding would fail to start up. This is because BasicHttpBinding doesn’t support this protection level by default (it can be enabled).

using System;
using System.Collections.Generic;
using System.Net.Security;
using System.ServiceModel;

namespace WcfServiceLibrary.Echo
{
    [ServiceContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
    public interface IEchoService
    {
        [OperationContract(IsOneWay = true)]
        void RegisterClient(Guid guid);

        [FaultContract(typeof(EchoFault))]
        [OperationContract]
        EchoMessage Echo(EchoMessage message);

        [OperationContract]
        List<EchoMessage> GetAllEchos();
    }
}

Using this protection level with a BasicHttpBinding endpoint results in an exception on the host.

System.InvalidOperationException: The request message must be protected. This is required by an operation of the contract [..]. The protection must be provided by the binding [..].

However if we change the endpoint binding to, for instance, wsHttpBinding, the service runs fine. This is because WsHttpBinding supports the EncryptAndSign protection level by default.

Using WCF Typed Fault Exceptions

In the previous example a generic FaultException was thrown and handled by the client. What if you want to throw a more specific exception? This is where typed fault exceptions come in, or FaultException(TDetail). First we create a new datacontract representing the fault type we want to throw.

using System.Runtime.Serialization;

namespace WcfServiceLibrary.Echo
{
    [DataContract)]
    public class EchoFault
    {
        [DataMember]
        public string Message { get; set; }

        [DataMember]
        public string Details { get; set; }
    }
}

On the interface of the service operation we want to use this fault type, we use the FaultContract attribute specifying the new type. The service now exposes the typed fault as a possible fault thrown from the Echo operation.

using System;
using System.Collections.Generic;
using System.ServiceModel;

namespace WcfServiceLibrary.Echo
{
    [ServiceContract]
    public interface IEchoService
    {
        [OperationContract(IsOneWay = true)]
        void RegisterClient(Guid guid);

        [FaultContract(typeof(EchoFault))]
        [OperationContract]
        EchoMessage Echo(EchoMessage message);

        [OperationContract]
        List<EchoMessage> GetAllEchos();
    }
}

In the operation implementation we can now throw the typed fault of type EchoFault.

public EchoMessage Echo(EchoMessage message)
{
    if (message.Text == "Fault")
        throw new FaultException<EchoFault>(
            new EchoFault
                {
                    Message = "Echo fault thrown",
                    Details = "More details here"
                });

    //...

    return message;
}

On the client side we can now access the typed fault in the Detail property of the FaultException.

using System;
using System.ServiceModel;
using EchoClientConsole.EchoServiceReference;

namespace EchoClientConsole
{
    internal class Program
    {
        private static void Main()
        {
            var channel = new EchoServiceClient(
                "WSHttpBinding_IEchoService"
                );

            try
            {
                channel.Echo(new EchoMessage
                                 {
                                     Created = DateTime.Now,
                                     Text = "Fault"
                                 });

                Console.ReadLine();
                channel.Close();
            }
            catch (FaultException<EchoFault> exception)
            {
                Console.WriteLine(exception.Detail.Message);
                Console.WriteLine(exception.Detail.Details);

                Console.ReadLine();
                channel.Abort();
            }
        }
    }
}

Wcf Typed Fault Exception Console

Explicit WCF SOAP Faults with FaultException

In the previous example I enabled includeExceptionDetailInFaults in app.config to get some more details on exceptions thrown over the wire. While this is nice for developing once the service hits production this can breach security. This is why it is set to false by default. There are, however, many times that you want to inform the client of an error. You can do this explicitly by throwing a FaultException. The EchoService Echo operation would look something like this:

public EchoMessage Echo(EchoMessage message)
{
    if (message.Text == "Fault")
        throw new FaultException("FaultException thrown!");

    message.Invoked = DateTime.Now;
    message.Text = String.Concat(
        Enumerable.Repeat(message.Text, 3));

    _echoList.Add(message);

    return message;
}

On the client side the FaultException is rethrown so we are able to catch and handle it.

using System;
using System.ServiceModel;
using EchoClientConsole.EchoServiceReference;

namespace EchoClientConsole
{
    internal class Program
    {
        private static void Main()
        {
            var channel = new EchoServiceClient(
                "WSHttpBinding_IEchoService"
                );

            try
            {
                channel.Echo(new EchoMessage
                                 {
                                     Created = DateTime.Now,
                                     Text = "Fault"
                                 });

                Console.ReadLine();
                channel.Close();
            }
            catch (FaultException exception)
            {
                Console.WriteLine(exception.Message);

                Console.ReadLine();
                channel.Abort();
            }
        }
    }
}

WCF Fault Exception Console