Monday, April 27, 2009

Confusing Errors Using WCF Transport Security With Client Certificates

While prototyping a WCF service last week I ran into a number of confusing security-related errors on both the client and server. My setup was as follows:

  • Windows 2008 Server
  • IIS 7
  • .NET 3.5
  • basicHttpBinding/transport security/client certificates

Most of the errors that can result from this setup have been documented elsewhere. This detailed post by Imaya Kumar does a great job of walking through the overall configuration and explaining the error messages caused by various misconfigurations. However, I did run into one client error that wasn't specifically documented anywhere else:

System.ServiceModel.Security.MessageSecurityException: The HTTP request was forbidden with client authentication scheme 'Anonymous'. ---> System.Net.WebException: The remote server returned an error: (403) Forbidden.

This error actually indicates that your client certificate could not be validated by the server. In my case I was using self-generated client and root certificates, and I had inadvertently uninstalled the root certificate from my server.

A related gotcha is that setting a particular clientCertificate.certificateValidationMode for your service is meaningless when using transport security. For example, the "PeerTrust" value in the configuration snippet below is ignored. Client certificates seem always to be validated using the PeerOrChainTrust method when using transport security with the http bindings.

   1: <behaviors>
   2:   <serviceBehaviors>
   3:     <behavior name="TransportBehavior">
   4:       <serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="false" httpsHelpPageEnabled="true" />
   5:       <serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" />
   6:       <serviceCredentials>
   7:         <clientCertificate>
   8:           <authentication certificateValidationMode="PeerTrust" />
   9:         </clientCertificate>
  10:       </serviceCredentials>
  11:     </behavior>        
  12:   </serviceBehaviors>
  13: </behaviors>

Thursday, April 16, 2009

New Simple Savant Release

I've just released Simple Savant v0.2 at CodePlex. This version brings the library up to date with the latest Amazon SimpleDB features and also completes the baseline feature set. (See this post for an introduction to Simple Savant.) The new release adds support for:

This release also includes a couple of breaking changes to reduce the potential for ambiguous or unsafe behavior in certain circumstances:

  • Select operations now return all available results by default. Previously Savant returned just the first page of results by default.
  • Savant now only limits the number of requests sent to SimpleDB on select operations rather than attempting to return a precise number of results. This seemed the best approach considering the potential confusion when using Savant with selects containing the "limit" keyword. Use SelectCommand.MaxResultPages to limit the number of result pages requested from SimpleDB in a single call.
  • DeleteAttribute requests are no longer used to delete null item properties on put operations. This not only degraded performance but could also lead to inconsistent data states. There are now two configuration options available for null property handling. Null item properties can either be ignored--meaning you become responsible for explicitly deleting attributes for null properties on put operations OR Savant can manage this for you by storing null properties as a single null character (\0) in SimpleDB.

Partial Object Operations

Partial object operations are useful when you need to put, get, select, or delete a small number of item attributes. For example, if we were working with a Person domain and needed to update just the birth date and email address for a large number of people we could do so with the code below. The example first puts new values for the two attributes and then gets just the birth date.

   1: PropertyValues values1 = new PropertyValues(person.Id);
   2: values1["BirthDate"] = new DateTime(2000, 1, 1);
   3: values1["EmailAddress"] = "mike@example.com";
   4: savant.PutAttributes<PersonItem>(values1);
   5:  
   6: PropertyValues values2 = Savant.GetAttributes<PersonItem>(person.Id, "BirthDate");

Scalar Selects

Scalar selects not only provide support for count(*) queries, but also make it easy to read a single scalar value with a minimal amount of code. In the example below we're selecting just the birth date for a person with a specific email address. If this method is used with a select query that returns extra items or attributes they will be silently discarded.

   1: DateTime? birthDate = (DateTime?)
   2:     savant.SelectScalar<PersonItem>("select BirthDate from Person where EmailAddress = @EmailAddress",
   3:                                     new CommandParameter("EmailAddress", "mike@example.com"));

BatchPutAttributes

Amazon's new BatchPutAttributes operation supports transactional puts of attributes for multiple items in the same domain. This is used automatically when you invoke SimpleSavant.Put() with multiple item parameters. For example, we could ensure that Tom, Dick, and Harry are either all in or all out of SimpleDB with the code below.

   1: PersonItem tom = new PersonItem();
   2: PersonItem dick = new PersonItem();
   3: PersonItem harry = new PersonItem();
   4: savant.Put(tom, dick, harry);

Multi-valued Command Parameters

Finally, it's now possible to provide multiple command parameter values for use with "in" clauses. The parameter values are expanded to a comma-delimited list for execution against SimpleDB. In the code snippet below we're using this feature to count the number of people in our domain whose height is denoted in inches or meters.

   1: CommandParameter parameter = new CommandParameter("HeightUnit", null);
   2: parameter.Values = new List<object> {HeightUnit.Inches, HeightUnit.Meters};
   3: int count = (int) savant.SelectScalar<PersonItem>("select count(*) from Person where HeightUnit in (@HeightUnit)",
   4:                                     parameter);
 
Header photo courtesy of: http://www.flickr.com/photos/tmartin/ / CC BY-NC 2.0