/WSDK

comment(s) 

Web Service Development Kit (Tech Preview)

http://www.brains-N-brawn.com/WSDK 11/4/2002 casey chesnut

.NET is certainly about web services ... you can divine this by every single .NET book having at least one Web Service chapter (no matter what the topic). Those intro chapters end up being pretty thin, because Web Services are evolving. The web service stack (XML, SOAP, XSD, WSDL, UDDI) continually moves to better standardization and interoperability. While 2nd gen GXA WS specs are being worked on with preview implementations like the WSDK  to support more complex applications. This article details my experience working with the WSDK samples (pitfalls, workarounds, and extensions)

The WSDK (Web Service Development Kit) was released late August. It is currently a Technical Preview. It has a lower level API for building SOAP messages on clients and a higher level API for use in ASP .NET Web Services. It does not explicitly work with .NET Remoting Web Services, but from a newsgroup posting it looks like that has been attempted with some success using the lower level API. The protocols it supports are WS-Routing (WS-Referral), WS-Security, DIME (WS-Attachments). In the future it will likely support WS-Inspection, WS-Coordination, WS-Transaction, BPEL4WS. From the newsgroup, also saw mention of a WS-Authorization? And what happened to WS-License?

Resources

There are a number of great articles on the WSDK. Will attempt to build off of these and take them a little further

Programming with the Web Services Development Kit Technology Preview (MSDN)
Test-Drive Microsoft's New Web Services Development Kit
Using WS-Security with the Web Services Development Kit Technology Preview (MSDN)
Dig Into WS-Security With the WSDK
Use X.509 Certificates With the WSDK

microsoft.public.webservices.wsdk (MSDN)

This was my 1st exposure to the WSDK, so we will go through the samples provided. These sections start off easy and become more difficult. The WSDK has a decent help file and comes with sample services and clients for most of the functionality that it provides. The business logic exposed by the web services is the de facto addition algorithm. Also, the code is provided as-is (C# only) with build scripts. Some of the article links above provide code in VB .NET. To make it easier to work with, I moved all the samples into a VS.NET solution as individual projects (source available below)

SumService

Nothing special here, this service and client are WSDK free. It is just provided as a means of comparing traditional ASP .NET Web Services with WSDK Web Services. Only code change was to add an app.config <remoteUrl> element to let me specify the URL for the auto-generated web service proxy client to point to

<appSettings>
  <!--add key="remoteHost" value="http://external.machine/" /-->
  <add key="remoteUrl" value="http://localhost/sumservice/sumservice.asmx" />
</appSettings>

The service is also used by the Routing sample below

Stacking SoapExtensions

From AXIS (Java Web Services), had been meaning to try out the stacking of SoapExtensions for a while. SoapExtensions provide access to SoapMessages before and after a SOAP Message is deserialized (request) and serialized (response). They can be hooked in on both the client (proxy) and server (WebMethod) sides. Previously, I'd always only used a single Extension, but AXIS showed me the value of having a more modular design, which would be possible with multiple Extensions; so I had to try it out in .NET. Made some simple Extensions from Rob Howards sample and decorated the service WebMethod and client proxy call with the following Attributes

[TraceExtension()]
[MiddleExtension()]
[OtherExtension()]

On both the client and the server, outgoing messages occur top-down and incoming messages are bottom-up. The preceding would result in

Here, it was done with Attributes, which is code change; but the <soapExtensionTypes> of the web.config (see DIME section below) demonstrates how the same can be done just my making changes to web.config for the server and app.config for clients. Thought AXIS was ahead in this one aspect until discovering this config element ... this provides a ridiculous level of flexibility and power!

On a related note, there are also <soapExtensionReflectorTypes> and <soapExtensionImporterTypes> config elements. The 1st time I saw them was here: http://www.newtelligence.com/wsextensions/index.aspx (early implementations of WS Security and Transactions). Have more playing to do before fully understanding them; and then more time after that determining when is the appropriate time to use SoapHeaders, attribute SoapExtensions at a method level, SoapExtensions at the config level, HttpModules, HttpHandlers, etc...

WS-Routing / WS-Referral

First off, the standardization of these specs are questionable. They have yet to find a standards body home, and IBM does not acknowledge them. That said, we definitely need some spec to provide the functionality they offer. WS-Routing is used to specify the path of a SoapMessage. It is a SoapHeader that specifies the path the message should take, used on both the client and server. It can even specify the return path. WS-Referral is used for dynamic routing by a SOAP router to a service, and for messaging between SOAP routers. It is observable in the referralCache.config file of the sample. NOTE These are a spin off from the early HailStorm / .NET MyServices implementation (/myVoices). Have not heard anything about them lately ... assume their still going through rework to let people host and deploy on their own? Did see mention that the next versions of Exchange / Office might have similar types of functionality :) <rant> That was my guess early on when it was shot down ... MS will start using .NET MyServices in their products, then people will be whining about how unfair that is, and MS will be like "you got what you asked for" </rant> Reading about mobile applications and the need to balance personalization and privacy ... .NET MyServices was the perfect tool to support this, now everybody is just going to roll their own :(

The routing service does not include any code, it is entirely configuration based. The initial config file is the web.config. Specificlly for SOAP router functionality, it adds an HttpHandler

<httpHandlers>
  <!--add verb="*" path="sumservice.asmx" type="Microsoft.WSDK.Routing.RoutingHandler, Microsoft.WSDK, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /-->
  <add verb="*" path="*.asmx" type="Microsoft.WSDK.Routing.RoutingHandler, Microsoft.WSDK, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</httpHandlers>

It also specifies a path to the referralCache

<microsoft.wsdk>
  <referral>
    <cache name="referralCache.config" />
  </referral>
</microsoft.wsdk>

After a SOAP route has been executed, the referralCache.config file will be locked. From the docs, to update the referralCache, you should create another cache file with a different name in the same dir and then change the file that web.config points to. Effective, but I hope they end up making it behave the same was as web.config; instead of indirectly through the web.config. Another good point is to keep the .config, since that will be protected by IIS automagically. NOTE the microsoft.wsdk custom config section

On the server side, a SOAP request will come in. The HttpHandler will then intercept with the Microsoft.WSDK.Routing.RoutingHandler. It compares the incoming URL with the referralCache.config and routes the SOAP request to the appropriate server (in this case, the virtual dir of SumService above). The service returns the SOAP response to the SOAP router, and the SOAP router sends it on to the client

The client is real simple too (the other clients are modeled in this same fashion too). It consists of 3 files: AddClient.cs, AddNumbers.cs, and AppBase.cs. AddClient.cs inherits from AppBase.cs. All AppBase.cs does is work with command line arguments and configure the web service proxy URL based on a config file change. AddClient.cs just spits out console usage text and makes the call to the web service. AddNumbers.cs is the class file of an auto-generated proxy from WSDL.exe. The only modification to the proxy is that it now inherits from Microsoft.WSDK.WSDKClientProtocol. There is no specific client code to insert the WS-Routing header. It just makes the call, and the WSDKClientProtocol class takes care of it. NOTE: you cannot 'Add a Web Reference' with a link to a SOAP router. You will get a 'SOAP message expected' response. The client can generate the proxy off the URL that it will ultimately be directed to (in this case SumService), and then change the proxy URL to point to the SOAP router

From the newsgroup, the URL specified in referralCache.config need some care. If the client uses an IP to your SOAP router and your referralCache.config specifies domain name, then they will receive an 'Endpoint Not Supported' SoapFault. Best practive will be to specify both, since CF .NET B1 does not always work so well with DNS lookups and works much more predictably with IP addresses. On an even nastier note (BUG) it is case sensitive, although the spec calls for case insensitivity. If the client calls for 'SumService.asmx' and the referralCache.config has 'sumservice.asmx', then you will get the same SoapFault. Ports and other URL tricks should be considered as well. NOTE that I relaxed the HttpHandler from 'sumservice.asmx' to '*.asmx' to help reduce variables when playing with this

Tracing

This isnt a sample, just a nice little feature of the WSDK to save off SOAP messages to a log file

<microsoft.wsdk>
  <diagnostics>
    <!--trace enabled="false" /-->
    <trace enabled="true" 
		input="C:\_DEV\cfWSDK\Traces\RoutingClientInput.xml" 
		output="C:\_DEV\cfWSDK\Traces\RoutingClientOutput.xml" />
  </diagnostics>
</microsoft.wsdk>

This config works on both the WSDK clients, SOAP routers, and services

From the Routing sample client above: Out / In / Fault. The 'path' element of the output is the routing info, the reverse path info is not returned as the input from the service in this instance

On the server side, their is another config feature to return detailed error messages

<microsoft.wsdk>
  <diagnostics>
    <faultDetail enabled="true" />
  </diagnostics>
</microsoft.wsdk>

Also, on your browser, turn off friendly Http Error messages that hide SOAP Fault details like this

In IE, uncheck: Tools-Internet Options-Advanced-Browsing-Show friendly HTTP error messages

Finally, there are some error messages that a WSDK services will write to the Event Viewer. Looking through my Application log, there are a chain of 'crypt32' errors having to do with not being able to validate an issuing CA as a trusted authority. Did not discover this til afterwards, but it might be worthwhile to put fullDetail=true on the X509Signing service, and then run through it with the different certificates to see why I kept getting 'InvalidSecurityToken'

DIME / WS-Attachments

These are joint MS / IBM specs that have been submitted to IETF. DIME is overall message format and can be comprised of a SOAP message and 0 or more Attachments. For DIME and all other WSDK protocols, an HttpModule is added in the web.config

<httpModules>
  <add name="WSDK" type="Microsoft.WSDK.HttpModule, Microsoft.WSDK, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</httpModules>

Specifically for DIME, a SoapExtension is added as well. 1st time I had seen a SoapExtension defined in this manner

<webServices>
  <soapExtensionTypes>
    <add type="Microsoft.WSDK.Dime.DimeExtension, Microsoft.WSDK, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
  </soapExtensionTypes>
</webServices>

The client take a users input as a string and adds it to the request as a DimeAttachment. If you look at the Trace info, it only serializes the SOAP request and not the Attachment. The Service gets the request, reads the Attachment into a string, and returns the string directly in a SOAP response

Now passing around strings is cute, but binary files is more practical... so I browsed around (for hours) til finding an appropriate test picture of some Hooters girls (dressed as cops with handcuffs, for Halloween of course). Then I modified the client to send the picture as a DimeAttachment

string hootersFile = @"C:\_DEV\cfWSDK\AttachmentsClient\HootersCops.jpg";
FileInfo fi = new FileInfo(hootersFile);
FileStream fs = fi.OpenRead();
byte [] bytes = new byte[fs.Length];
int numRead = fs.Read(bytes, 0, (int)fs.Length);
fs.Close();
MemoryStream buffer = new MemoryStream(bytes);
DimeAttachment attachment = new DimeAttachment("image/jpeg", TypeFormatEnum.MediaType, buffer);
serviceProxy.RequestSoapContext.Attachments.Add(attachment);

And the server to persist the attachment to disk and return some dummy string

DimeAttachment da = HttpSoapContext.RequestContext.Attachments[0];
BinaryReader br = new BinaryReader(da.Stream);
byte [] ba = new byte[br.BaseStream.Length];
int numRead = br.Read(ba, 0, (int)br.BaseStream.Length);
br.Close();
string filePath = Server.MapPath(".") + @"\" + DateTime.Now.Ticks.ToString() + ".jpg";
FileInfo fi = new FileInfo(filePath);
FileStream fs = fi.OpenWrite();
fs.Write(ba, 0, numRead);
fs.Close();

So now it packages the SoapMessage and binary Attachment into a DimeMessage. In this manner, the image does not have to get encoded and stuffed into XML ... causing the message size to bloat, as well as unnecessary use of CPU on both the client and server. Another appropriate test would be to pass around SOAP messages as Attachments (i.e. XML messages do not like embedded XML nessages)

Trace: Out / In NOTE how empty the Out message is, because most of the data is in the Attachment that is not serialized by Trace. It would be nice if Trace was extended to serialize Attachments and the entire HTTP request / response package

WS-Security

The rest are all related to WS-Security, which is probably the most important spec to everybody at the moment. It is proposed jointly by MS / IBM / Verisign and is submitted to OASIS, there is also an Addendum and numerous other specs that it relies on

TimeStamp

This one is way too easy. Look at the other trace messages to see what one looks like. The things to notice are the Creation and Expiration time. A client sets the ttl (Time-To-Live) on a SoapRequest like so: serviceProxy.RequestSoapContext.Timestamp.Ttl = 10000; /* 10 seconds */ and a server sets it on a SoapResponse: responseContext.Timestamp.Ttl = 10000; /* 10 seconds */ The only fun you can have is to bump the ttl down to 1 millisecond so that it expires as soon as you send it and you get a Fault back

UsernameSigning

The WSDK supports signing of a message 3 different ways (Username Token, Custom Binary, and X.509 Certificate). Username signing is the easiest, with a couple options of its own to not send the password, hash the password, or send the password as plaintext. The WSDK sample code does a hash and compares from the class implementing IPasswordProvider specified in the web.config

<microsoft.wsdk>        
  <security>
    <passwordProvider type="Microsoft.WSDK.QuickStart.UsernameSigning.PasswordProvider, UsernameSigning" />
  </security>
</microsoft.wsdk>

Trace: Out / In / Fault NOTE how verbose the out message is

SymmetricEncryption

Encryption also has 3 different options (Symmetric Shared Secret, Custom Binary, and Asymmetric X509). Symmetric is the easiest. It works by having a shared key (i.e. password) that exists on both the client and the server. The message can be encrypted and decrypted with the same password

<appSettings>
  <add key="symmetricKey" value="EE/uaFF5N3ZNJWUTR8DYe+OEbwaKQnso" />
</appSettings>

This is secure until a 3rd party discovers the shared secret. NOTE symmetric encryption is fast, but less secure then Asymmetric. Typically the usage is to symmetrically encrypt the content (which could be large) and then assymetrically encrypt that symmetric key with a public key and pass it along with the message. On the other end, they use their private key to decrypt the symmetric key, then use that key to decrypt the contents of the message. This gives strong security, the symmetric keys are typically random and short lived, as well as some performance. For decrypting, a class that implements the IDecryptionKeyProvider needs to be specified in the web.config

<microsoft.wsdk>        
  <security>
    <decryptionKeyProvider type="Microsoft.WSDK.QuickStart.SymmetricEncryption.DecryptionKeyProvider, SymmetricEncryption" />
  </security>
</microsoft.wsdk>

The given sample ends up encrypting the SoapRequest from the client and then returns the result as plaintext. The other scenarios are that the request could be plaintext and the response ciphered, or the request and response could both be ciphered. I extended the sample to do the latter

First, updated the SymmetricEncryption service sample to send encrypted data back.
Cut/paste the GetEncryptionKey() method from the related Client into the service, then called:
EncryptionKey key = GetEncryptionKey();
responseContext.Security.Elements.Add(new EncryptedData(key));
before the return a + b; in the WebMethod

Second, updated the SymmetricEncryption client to decrypt that returned data.
Cut/Paste the DecryptionKeyProvider class from the service into AddClient.cs and added using System.Security.Permissions;
Also cut/paste the <decryptionKeyProvider ... /> into the app.config file of the client and modified the assembly name.

Very little code mods with the results being both the request and response were ciphered: Out / In

X509Signing

This is where the fun (i.e. pain) begins, if you look at the newsgroups you will see more of the same, as well as articles above that only concentrate on dealing with signing and encrypting with X509 certificates. The hardest part is getting a certificate that will work with the WSDK. Tried a # of certificates

The client pops up a dialog for you to choose which Cert you want to sign with. The WSDK is unfiltered and will let you choose inappropriate certs. The Roger Jennings sample code goes a little further and gives you prompts as to which certs are good for signing and which are good for encrypting. The MS dialog is shown below

All of these failed for me either because I could not sign with it OR could not verify the certificate. Verification failed either because of a nuance of the cert OR because the issuing CA was not trusted. Makecert, OakLeaf, and barmala could not be verified. Aaron Clauson, whom went through this before me seemed to think it was because of some extension provided by the MS CA. Thawte and Verisign would not sign with the WSDK sample code. Verisign would sign with the Roger Jennings VB code, but then would not verify. From the newsgroup it looks like some people might have got Makecert to work, but I could not determine how. But the OakLeaf ones seemed the most successful. They were obtained from an MS test CA server (SECTESTCA1) that was no longer available by the time I was writing this article. So they were from an MS CA, but they could still not be verified because I did not have the cert from the issuing test CA to make it a Trusted Authority. Finally the apps that got in earlier and were working, should probably stop working because that CA cert expired November 3rd of this year. All in all, I was stuck. Another tip from Aaron and he showed me a web.config that would make the WSDK skip the verification of the certs (spent nearly 2 days coming to that conclusion) NOTE the certs to sign are for the CurrentUser

<microsoft.wsdk>
  <security>
    <!--x509 storeLocation="CurrentUser" verifyTrust="false" allowTestRoot="false" /-->
    <x509 storeLocation="LocalMachine" verifyTrust="false" allowTestRoot="false" />
  </security>
</microsoft.wsdk>

That partial success was enough for me to move on: Out / In / Fault NOTE Get used to InvalidSecurityToken

If you have more patience it might be worthwhile to try the updated Makecert utility from the Platform SDK. The newsgroup has a good posting on what the command line arguments should be. Also, if you have access to your own MS CA. Issue yourself certificates from it, and get the CA certificate that it is a Trusted Authorigy. That should work as well. Finally, try setting fullDetail= true for error messages on the service and check the Event Viewer to see if it posts more meaningful errors than IST.

AsymmetricEncryption

The 1st thing I did was add the web.config setting to turn off verification of X509 certificates, just in case :) And this requires 2 certificates. 1 cert has to be both in the CurrentUser for the client and LocalMachine for the server. CurrentUser only needs the public key to encrypt, while LocalMachine needs the private key to decrypt. As stated by Roger Jennings, the user would also likely sign with their own certificate from CurrentUser. After the X509Signing ordeal, went straight to the OakLeaf provided certificates and they worked right off. The CA Trusted Authority cert was not needed for encryption and decryption. Like the original SymmetricEncryption sample, the WSDK sample sends the request ciphered and returns the response as plaintext: Out / In 

Source

Hopefully I've not included my personal certificates. C# source NOTE other then my extensions and tweaks, this is just a VS .NET packaging of the sample code included with the WSDK tech preview. You will need to install it to get these to work as well

Future

Should have an immediate follow up article to this, as well as more article(s) once the WSDK gets out of tech preview stage. Would like to see Custom Binary code for Signing and Encryption. WebSphere is supposed to have some bits to support some of these specs now too, so some early interoperability would be interesting. The Tablet PC SDK is on the list, and now the Mobile .NET Beta 1.1. NOTE that bNb will be going offline sometime late November, so save off any articles you are interested in and subscribe to the email list if you want to be notified when its back online. Later