Wednesday, September 20, 2017

Microsoft 70-487: Secure a WCF service

Exam Objectives

Implement message level security, implement transport level security; implement certificates; design and implement multiple authentication modes


Quick Overview of Training Materials



There are two fundamental security modes at the binding level in WCF (what the Programming WCF Services book calls "Transfer Security")- Transport and Message, which can be used individually or in certain combinations (TransportWithMessageCredential is a more flexible version of Transport, and Both is a belt-and-suspenders mode where a secure message is sent through a secure transport).  Aaron Skonnard's "WCF Design Concepts" PluralSight course probably does the best job of explaining and demonstrating these two security modes, while the "WCF End-to-End" course has a heavier emphasis on the authentication piece.

The purpose of securing the transfer of messages is three fold:  One is to ensure the confidentiality of the message, i.e. only the intended party should see the message.  Second is to ensure the integrity of the message, meaning that a third party cannot modify the message while it is in flight.  The last purpose of transfer security is authentication, (in the WCF book called "mutual authentication"), meaning either end cannot be spoofed or impersonated.  This is subtly different from what we classically think of as "authentication"; it is possible to have a mutually authenticated transport channel (over HTTPS, for example), and still be required to provide a username/password credential to "authenticate" with the application.

Message Security


Message security works by encapsulating the security information within the message itself.  The options available for this kind of security vary based on the binding in use.  The WsHttpBinding has many options available:

  • Client Credential Type: Certificate, IssuedToken, None, UserName, Windows
  • Algorithm Suite: various combinations of Basic, TripleDes, Rsa, and Sha
  • Establish Security Context: (bool) determines whether the channel establishes a secure session.
  • Negotiate Service Credential: (bool) when "false", the service credential is shared with the client "out of band", when "true" the credential is "negotiated" over a series of SOAP messages.
Compare this to BasicHttpBinding message security, which only includes the algorithm suite, and two credential types (username and certificate), with no support for a secure session or negotiated credential.

In order to see this in action, it is useful to turn on WCF tracing.  Skonnard demonstrates the results in his course but I don't recall him going over how to set it up, so I had to figure that bit out for myself.  To save you the trouble, add the following bits of config to your service:

This goes inside <system.serviceModel>:


    <diagnostics>
      <messageLogging
           logEntireMessage="true"
           logMalformedMessages="false"
           logMessagesAtServiceLevel="false"
           logMessagesAtTransportLevel="true"
           maxMessagesToLog="3000"
           maxSizeOfMessageToLog="2000000"/>
    </diagnostics>



This goes under the main <configuration> element:

  <system.diagnostics>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true" >
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
      <source name="System.ServiceModel.MessageLogging">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
      <source name="myUserTraceSource"
              switchValue="Information, ActivityTracing">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="xml"
           type="System.Diagnostics.XmlWriterTraceListener"
           initializeData="log.svclog" />
    </sharedListeners>
  </system.diagnostics>


This setup will create a file called "log.svclog" in the bin/Debug directory when you run the service.  Most of the information seems to be written when the service is closed, so that is the best time to open the file with the SvcTraceViewer program, which is part of the Windows SDK.  I found it under "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\" on my Windows 10 laptop, and on Windows 7 it was more or less the same.

Running the demo service (which is just a simple echo service, nothing to write home about), the service is clearly able to read the message sent from the client:



However, when we look at he logs, we see that the message is actually encrypted as it comes off the wire:



One interesting note here:  when I first started playing with this, all the messages seemed to be unencrypted, which was frustrating because I saw in Skonnard's demo that it was supposed to be encrypted.  Figured out that the reason had to do with when I logged the message.  I was logging at the service, and by that time the message has already been decrypted.  By changing the logging to happen at the transport, I was seeing the expected results.

There are several advantages that message security provides.  First, message security provides end-to-end security, which can be important in situations where there is an intermediary (such as a router) between the sender and receiver of the message.  This property also allows message security to cross transports, i.e. it can use different transports for different "hops" and remain secure.

Message level security is also flexible, in that it allows you to sign and encrypt different portions of the message.  As an illustration, is part of the message is needed by the router (say, an Action header), that part of the message can be signed (to ensure integrity), but left unencrypted.

The primary drawbacks of message based security are performance.  Encrypting and signing add overhead to the marshalling and unmarshalling process, slowing down the service.  Furthermore, because the entire message must be held in memory to apply message security, working with large files becomes basically infeasible.



Transport Security


Transport security seeks to secure messages by securing the channel through which they are transmitted, rather than securing the messages themselves.  Most of the literature makes the statement that transport security is a "point to point" security mechanism, but a post on StackOverflow points out that this is somewhat misleading.  Transport security is point to point in the sense that it only secures a message between a client and an intended endpoint.  If the ultimate destination of the message is behind that first destination (such as in routing scenarios), this is the sense in which it is true that transport security is "point to point".  Messages are still secure as they traverse various switches and routers on their way to the intended destination.  It may be a bit pedantic, but I thought it was a good point...

Transport security is the default for the Net* Bindings (TCP, MSMQ, NamedPipes). and is also an option for WsHttp and BasicHttp (using https).  Transport security is not available on the WsDualHttp binding.

The chapter from the Improving Web Service Security posits that Transport security is best suited for direct communication applications (no routers), or intranet scenarios, citing interoperability (no need for WS-Security) and performance.  The disadvantages include the point to point criticism (it does explain it as point to point between logical application nodes), limited credential support, and (ironically) interoperability (transport dependent security mechanisms such as NTLM and Kerberos).



My post on creating the Windows Authentication Proxy also provides an example of using Windows transport security, as well as illustrating the shortcomings of using transport security in the first place (our Java based API platform did not support Kerberos authentication... boo!).

I found an example on the MSDN blog of running a self-hosted WCF service (basically every demo app I've created) using SSL.  There is also an article on Code Project doing essentially the same thing, though I question whether that one is doing what he thinks it is, since he is setting clientCredentialType to "None".  I'll get deeper into how to set up and use certificates in that section below.



Protection Level


While not specifically called out in the objectives, I thought it valuable to point out protection levels.  The protection level is an property that can be set on Service, Operation, and Message contracts that sets the minimum amount of security required of the binding over which messages are passed.  The values are None, Sign, and EncryptAndSign.  Both Transport and Message based security mechansisms can satisfy these requirements, and most WCF bindings implement the maximum protection level by default.  For example, NetTcpBinding uses transport security, and the WsHttpBinding use message security by default, and both would satisfy the EncryptAndSign requirement.


[ServiceContract(ProtectionLevel = ProtectionLevel.Sign)]
public interface ICalculator
{
    [OperationContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
    double Add(double a, double b);
}

In the above snippet, the default protection level for the service is "sign", while the "Add" operation requires encryption.  The MSDN article "Understanding Protection Level" points out a few nuances of using protection levels and is a good use of three minutes.

One last point on protection level, is that it is always set programmatically.  It allows the service specification (that is, the contract) to stipulate the constraints that must be met by the bindings, which may not, by their extensible nature, be known to the service creator.



Certificates


Ah yes, certificates, my hated nemesis.  Long have I done battle with you on the fields of strife and frustration!  But, my long struggles with you have made my will stronger... my blades sharper.  I have come to respect your warrior's spirit... and today, I will smite thee!

oooo-kay, enough of that...

Silly as the certificate samurai shtick it, it has been a long road for me to understand certificates to an appreciable degree.  I'm certainly not an expert, mind you.  For 70-486, I touched on certificates in the Implementing a Secure Site post, and again when I was trying to use SSL on localhost for the Identity Federation post.  And while I never got around to blogging about it, I've had to wrestle with certificates and SSL with Java as part of my current job.



Create a Cert


The first step to working with certificates is to actually have a certificate.  The Implementing a Secure Site post outlines how to go about created a Certificate Signing Request, which is the first step toward getting a real certificate, but we don't need to go that far for the purposes of development, so I'm going to follow the steps in Microsoft's How to: Create Temporary Certificates document to create some dummy certs on my local machine (a process repeated in at least one other article I found).

Creating a local cert involves the use of a tool called makecert.exe. The guide actually has us create two keys: one will act as the "root authority" for the second, mimicking the way real certs are structured (with a "trusted" root certificate acting as the ultimate source of trust for commercial certs). I ran the following command in cmd, apparently it was already on my path:

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


I hit "None" for the password, since this is just for local development. To install this certificate as a trusted root authority, run "mmc" from program search, add the "Certificates" snap-in (I ran as "Local Computer"), right click on the "Trusted Root Certification Athorities" subfolder "Certicates", select "All tasks..." and finally, "Import".  The rest is a wizard that anyone should be able to follow.


With this done, run makecert again to created a signed certificate:

makecert -sk SignedByCA -iv TempCA.pvk -n "CN=SignedByCA" -ic TempCA.cer SignedByCA.cer -sr currentuser -ss My

Tada! Our very own cert to play with.  You can open it up with the "Crypto Shell Extensions" in Windows 7, I didn't test other platforms so YYMV, but this clearly demonstrates the artificial certification path we just created.  This tool also lets us copy the cert to a file, which will allow us to create a Base64 version of the key (probably a way of doing this with makecert directly...).




Use Cert to Secure Transport


The How To article kind of falls down for me at this point, since it is geared toward IIS hosting the service, and my demo is self hosted.  Fortunately, several of the other articles I found on using certificates are aimed at self-hosted services.

Now before any of this will work, it is necessary to set up the certificate just so.  Here are a couple of the things that tripped me up:

  • When I imported the certificate through mmc, it wasn't including the private key.  
  • If the certificate doesn't match the host name, you'll end up getting SSL warnings that will throw exceptions in WCF.
  • The certificate has to be added to the port your service will use (in my case, :10050).

The following command will add the certificate to the port:

netsh http add sslcert ipport=0.0.0.0:10050 certhash=THUMBPRINT appid={any guid}



This command kept giving me the following error:

SSL Certificate add failed, Error: 1312


Which was just soooooo helpful in debugging.  I figured it out, mostly through trial and error.  I also ended up recreating the certificate so that it would match my machine name, though I'm pretty sure if I created it with the name "localhost", I could use localhost in the endpoint address and have everything still work correctly.

makecert -sk <machine> -iv TempCA.pvk -n "CN=<machine>" -ic TempCA.cer <machine>.cer -sr currentuser -ss My


Setting the location (-sr) to "LocalMachine" threw an error, probably because I wasn't running that instance of cmd as Administrator, but once the cert was created, I opened it with Crypto Shell Extensions and selected "Install Certificate..."



You can see the different in mmc, where a cert that includes the private key has a little key icon on the image, and certs with only the public key do not:



Now that all the infrastructure is in place, it's just a matter of correctly configuring the service.  On the binding, set the clientCredentialType to "Certificate":

    <bindings>
      <wsHttpBinding>
        <binding name="secure">
          <security mode="Transport">
            <transport clientCredentialType="Certificate" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>


Also, create a service behavior with a "ServiceCredentials" element, setting the service certificate to look up the certificate we configured for that port, and setting the client validation mode to "ChainTrust" so that it will validate the certificate by checking for a trusted root authority:

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceCredentials>
            <clientCertificate>
              <authentication certificateValidationMode="ChainTrust"/>
            </clientCertificate>
            <serviceCertificate findValue="4e5d7ad83ebb155520320c0df53a7bf7c169fb84"
                                storeLocation="LocalMachine"
                                storeName="My"
                                x509FindType="FindByThumbprint"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>



Use Cert to Authenticate Client


The last bit of the puzzle is to set up the client.  The binding configuration matches what is on the service, while the behavior is somewhat different.  Instead of a service behavior, we use an endpoint behavior, and set a client credential.  This credential does not have to be the same certificate we set on the service.  In the snippet below, I'm also showing how to search for a certificate by subject name rather than thumbprint (which would be handy if I set up a "localhost" cert):

      <behaviors>
        <endpointBehaviors>
          <behavior name="cert">
            <clientCredentials>
              <clientCertificate  findValue="SignedByCA"
                                  storeLocation="CurrentUser"
                                  storeName="My"
                                  x509FindType="FindBySubjectName"/>
              
              <serviceCertificate>
                <authentication certificateValidationMode="ChainTrust"/>
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
    </behaviors>


Just make sure to set the "behaviorConfiguration" and "bindingConfiguration" settings on the endpoint.  And just like that, you have working certificate authentication.  Fantastic!




Authentication


Chapter 5 of  Improving Web Service Security covers authentication scenarios, and how authentication options are tied to the transfer security mechanism.  Authentication is also covered in-depth in Programming WCF. The Exam Ref skips it, surprise surprise.

The authentication methods described by Improving Web Services Security break down as follows (I've omitted "None"):

  • Transport security:
    • Basic
    • NTLM
    • Windows
    • Certificate
  • Message security
    • Windows
    • Username
    • Certificate
    • Issue Token

Basic, NTLM, and Windows (aka "Negotiate" inside Windows Server and IIS config) all use Active Directory as the underlying source of truth regarding identity.  What this means is that transport security boils down to two mechanisms for authentication: AD or X.509.  These are also available for message security, as well as Username, which allows verifying against AD, a membership provider, or some custom mechanism; and Issue Token, which utilizes a security token service.  

The examples for my Identity Federation post make pretty heavy use of simple STSs, and there are some examples of using Issued Token based security on WCF services (here and here and the main variations).   Windows authentication is the default for most bindings, and thus is sort of implicitly included in quite a few demos as well.  

Both the WCF End-to-End course and the WCF Security Guide include demos on using an ASP.NET membership provider with WCF.  How to: Use wsHttpBinding with Username Authentication is complete but it is nearly 10 years old.  How to: Use the ASP.NET Membership Provider is dated much more recent, but doesn't provide any guidance on setting up the database.  Interestingly, the article right after that one is a how to for creating a custom validator, which would provide for a great deal of flexibility in implementation.

The first step to use a ASP.NET membership provider is to set up the database.  Installing SQL Server Express and SSMS is simple enough, but I hit a bump looking for the developer command prompt; judging by the fact that there is a MSDN article on how to find it, I doubt I am the first person to have this issue.  Once located, run this command:

aspnet_regsql -S .\SQLExpress -E -A m

Which is basically just saying "set up the membership database":



That's basically the last unambiguous success I had with this demo.  I fought with the certificates again to some degree, which I thought was weird since I (thought) I configured transport security to not require a certificate.  Even when I set security to just "Message", it still wanted a certificate configured for the service.  I think I may have gotten it to a point where it was trying to validate the username and password, but the client kept throwing an exception complaining that a token could not be validated (which I think means it couldn't find the username/password).

If you get an error bitching about "remote host was forcibly disconnected" or some such thing, you may need to run netsh.  This bit me a couple times.

When I created a dummy in-memory membership provider, I was able to finally get everything to work together.  So it seems the last hurdle I was experiencing had to do with the SqlMembershipProvider acting up... which really doesn't surprise me at all.  Since this post isn't about correctly implementing SqlMembershipProviders, I think I got the gist across sufficiently.

Here is what the final config looked like on the service:

  <system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="wsHttpEndpointBinding">
        <security mode="TransportWithMessageCredential">
          <transport clientCredentialType="None" />
          <message clientCredentialType="UserName" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <services>
    <service behaviorConfiguration="ServiceBehavior" name="ServiceShared.Service">
      <endpoint address="WCFTestService" 
                binding="wsHttpBinding"
                bindingConfiguration="wsHttpEndpointBinding"
                name="wsHttpEndpoint" 
                contract="ServiceShared.IService">      
      </endpoint>
      <endpoint address="mex" 
                binding="mexHttpsBinding" 
                contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="https://localhost:10099"/>
        </baseAddresses>
      </host>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="ServiceBehavior">
        <serviceMetadata httpsGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
        <serviceCredentials>
          <serviceCertificate storeLocation="LocalMachine" 
                              storeName="My" 
                              x509FindType="FindByThumbprint" 
                              findValue="685d27be857bc38ca4bacdfab634084d720b7532" />
          <userNameAuthentication userNamePasswordValidationMode="MembershipProvider"
                                  membershipProviderName="InMemoryProvider" />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
     <diagnostics>
      <messageLogging
           logEntireMessage="true"
           logMalformedMessages="false"
           logMessagesAtServiceLevel="true"
           logMessagesAtTransportLevel="true"
           maxMessagesToLog="3000"
           maxSizeOfMessageToLog="2000000"/>
    </diagnostics>
</system.serviceModel>


One thing I found interesting, when examining the logs, is that the username and password weren't included with the actual request to the Echo operation.  Instead, a WS-Trust request preceeded it, which included the username and password (though the values were removed from the log).  One thing I further found odd is that the message itself was not encrypted anymore... well, not in the logs. Then I remembered that the security is happening in the transport layer, which is decrypted before it ever hits the logs.  I really don't feel like installing WireShark just to verify this, so I think, for now, I'll call it good.  Here are screenshots of the security token request, as well as the actual service call:





No comments:

Post a Comment