have written a number of articles about how to call advanced Web Services (WS) from the Compact Framework (CF). late last year, i put together the /spWSE article (with code) to work against a portion of the Web Service Enhancements (WSE) 2.0 Tech Preview. at a high level it supported WS-Addressing, WS-Attachments, and WS-Security, and could call about 1/2 of the WSE 2.0 Tech Preview samples. then i wrote a couple more articles (without code) to do WS-Eventing (/cfEvent), WS-ReliableMessaging (/cfReliable), and WS-SecureConversation (/cfSecConv). last month, WSE 2.0 was finally released. shortly after i started updating /spWSE to work against those new bits ... the end result being this article (with code). it still supports WS-Addressing, WS-Attachments, and WS-Security. it has also been extended to support WS-SecureConversation and can call almost all of the WSE 2.0 Release provided samples.
there are a number of changes on my side (as well as the WSE side). the main change to my code was to switch to using the OpenNETCF.Security.Cryptography namespace that is now a part of SDF 1.1. also, i added in the WS-SecureConversation implementation, plus minimal support for Filters and Messaging. with these changes, the sample code shows how to call almost all of the 21 samples that are included with WSE.
also had to account for breaking changes to make the jump from WSE Tech Preview to RTM. what i can remember off the top of my head: namespace strings changed. specifications changes for the move to OASIS. WS-Trust and WS-SecureConversation specs were updated. default symmetric encryption changed from TripleDES to AES128. became more strict in ordering of XML elements. supports signing or encrypting 1st. the Timestamp element was moved to a child of the Security element. the Timestamp element is signed instead of individually signing the Created and Expires elements. ReplyTo replaces the From element for WS-Addressing. WSE 2.0 also adds some new functionality such as encrypting with UsernameTokens and the use of DerivedKeyTokens. overall, these changes make WSE more secure ... so i cant complain
the most difficult change involves the default symmetric encryption changing from TripleDES to AES128. TripleDES has been around forever, while AES is the new DOD standard. Pocket PC and SmartPhone devices have TripleDES implementations through the CryptoAPI ... but not AES. this causes a number of problems. 1st) WSE does not dynamically detect encryptions. e.g. if a client sends in a request encrypted with TripleDES, but the web service has not been modified to expect TripleDES, then the request will fail. i think WSE should be configurable to strictly specify the encryption algorithm, as well as being able to dynamically adapt. 2nd) the service has to be manually changed to use TripleDES. e.g. if you ultimately want your service to be callable with TripleDES, then you will have to change code or a policy file to make it happen. you have been warned. 3rd) the token issuer for WS-SecureConversation cannot be changed when encrypting Entropy. it supports kw-aes128 only, and cannot be changed to use kw-tripledes. the good news is that you can call the token issuer without passing Entropy. this needs to be made configurable.
NOTE i believe it is possible to write an incoming Filter on the server side to have it dynamically change the encryption algorithm, so that a service could be called both by clients that support Aes128 and those that support TripleDES. else it should be possible to find a compatible Aes128 implementation to work on current devices. UPDATE see /cfAES for how to do AES128 on a device, as well as many other crypto algorithms. it has been tested and works to call the WSE 2.0 samples. finally, the next version of CE will have an AES implementation. i hope it also has stronger SHA implementations as well.
this section will list each Sample web service included with WSE 2.0. it will explain its out-of-the box behavior, what changes need to be made on the server-side, and where to look for the sample code on how to call it. the C# WSE samples install at: C:\Program Files\Microsoft WSE\v2.0\Samples\CS\QuickStart. the samples also come in the VB flavor. after choosing a language, each sample comes with a code and policy version. the code version shows what changes need to me made in code, while the policy versions use policy configuration files to isolate the business logic. most samples come with a client (sender) and a server (receiver) project as a part of each solution.
the device-side code is in a single Smart Device project, which i use as my test harness. you will have to refer to that sample code to see how to call the WSE samples. the images below are the different menus of my test application. i will refer to the Menus by number (1-4) and the MenuItems by name (e.g. 'Timestamp' in Menu 1).
this is basically a traditional ASMX web service. it does not really use WSE for anything interesting by itself. later on, it will be called indirectly through the Routing sample. the only thing worth noting at this point is that the WebService class has this Attribute: [SoapActor(http://localhost/RouterService/StockService.asmx)]. since devices cannot do localhost, this will need to be changed to IpAddress, MachineName, or DomainName. the HeadersHandler class also has a SoapActorUrl Property you can set if necessary, otherwise the SoapActorUrl will be set automatically on outgoing requests
this is a utility project that is used by the following samples. it provides a number of helper functions used by both the client and server pieces. i did not attempt to duplicate this code for CF
this sample passes a Timestamp element in the request. a Timestamp specifies when it was Created and when it Expires. this is useful to avoid replay attacks ... when signed. a Timestamp needs to be signed to avoid replay attacks, else a more sophisticated replayer can just modify the Timestamp and keep sending the same message. if it is signed, then the replayer cannot modify the Timestamp without breaking the Signature. NOTE this sample does not do signing.
there are a couple ways to call it. the 1st is to use a SoapHeader, demonstrated by the 'Timestamp' menu item in Menu 1. this requires you to add the SoapHeader to the web reference class during design and run time. the 2nd way to call it is to use a SoapExtension, demonstrated by the 'Extension' menu item in Menu 2. this requires adding the HeadersExtension Attribute to the web reference class. then you just call the web service and it will add the Timestamp and WS-Addressing elements to the message automatically. the HeadersHandler class has a SecondsToTimeout Property to configure. also, the HeadersHandler does check Timestamps in response messages to make sure that they have not expired.
NOTE the Smartphone emulator does not startup with its timezone set to the same timezone as your machine ... unless you happen to live at GMT London, Dublin :) if the calls are failing, i would check this 1st. you can set it in code by using the TimeZone class. see the Form1_Load method to see an example. also, if you leave an emulator running for a while, then the times will get out of synch. e.g. i've seen a SmartPhone emulator get out of synch by as much as 5 minutes in less than an hour of real time. if your Timestamp expires within a couple minutes, then this will cause them to fail. the fix is to shut down the emulator and restart it to get back in synch. finally, people are constantly asking about how to keep the device in synch with the service. one such way is to provide a GetDateTime() method on the WebService. when the client starts, it can call this method and use this time within the application to make sure that it is in synch. remember your calls will still fail when daylight savings time changes happen, so you should account for that in code. e.g. your web service might go through a daylight savings time change, and then the client calls will start failing. in X number of hours, the client might change as well, and then get back in synch ... or not
the sample is called Routing, which it does ... but not to be confused with WS-Routing for WSE 1. WS-Routing was a MS only specification that never took off, and is replaced by WS-Addressing for WSE 2. the main thing it does is to embed the endpoint information within the message itself. this decouples it from the transport, since each transport (HTTP, TCP, NNTP, SMTP, SMS, ...) has a different way of specifying the endpoint. see the WSE 1 section below to see how to do WS-Routing.
this one receives a call from the client. then it checks it referralCache.config, and forwards the request to the proper web service. it gets the response from the web service, and then routes it back to the client. the referralCache.config will have URLs with localhost, so you will have to change it to IpAddress/MachineName/DomainName. the SoapActor Attribute on the BaseStockService will have to be changed as well (read BaseStockService section above). you might need to be careful with capitalization of the URLs as well
this sample can be called by using SoapHeaders or a SoapExtension as well. the 'Addressing' menu item on Menu 1 shows how to use SoapHeaders. the 'Extension' menu item on Menu 1 shows how to use the SoapExtensions. the web reference class requires the same HeadersExtension Attribute used for the Timestamp sample above. once you set the Attribute, the code handles everything else automatically. this is the same programming model in WSE. you just make the call and it automatically adds the Timestamp and WS-Addressing elements to the Soap Header.
this would be used to pass binary files or xml documents to a web service. the alternate is to use CDATA sections or base64 encoding. the sample accepts a DimeRequest with an attached string. it reads that string, and then returns that string as the response. the sample does not require any modifications to be called by a device.
the 'Dime' menu item on Menu 1 shows how to call it. you will need to add the Dime.DimeExtension Attribute to the web reference proxy class, plus the DimeAttachmentCollections. at run-time you will then add DimeAttachments to the request collection, and check the response collection after the call has been made. you can also use the DimeDir Enum to specify that the call should be SoapRequest / DimeResponse, DimeRequest / DimeResponse, or DimeRequest / SoapResponse.
NOTE that DIME is the best we have right now, but is marked to be replaced by MTOM. the problem with DIME is that it is another layer outside of the SoapEnvelope, and the other WS-* specifications such as WS-Security do not say how they should be secured. so MTOM is a good thing. also, the Dime library has no dependencies; it can be used by itself if that is all that you need. finally, this same code can be used to make DIME calls to WSE 1.0 with no changes
this sample sends a request with a signed UsernameToken. the signature is done by deriving a 16 byte key from the UsernameToken, and the UsernameToken is sent without sending the password. the 16 byte key is because WSE now uses AES128 by default. if you modify the server to use TripleDES instead, then the client will have to be modified to sign with a 24 byte derived key. the code (and policy) to modify the server to use TripleDES follows:
ISecurityTokenManager stm = SecurityTokenManager.GetSecurityTokenManagerByTokenType(WSTrust.TokenTypes.UsernameToken); UsernameTokenManager userTokM = stm as UsernameTokenManager; userTokM.DefaultKeyAlgorithm = "TripleDES"; <securityTokenManager xmlns:wsse=".." qname="wsse:UsernameToken" > <keyAlgorithm name="TripleDES"/> </securityTokenManager>
the client code to make this call is at the 'XmlSig - Username' menu item of Menu 2. the web reference proxy class requires the WsExtension Attribute to be applied. if you have changed the server to use TripleDES, then you must change the NumKeyBytes Field of the XmlSigHandler to 24 bytes. something new for the WSE 2.0 Release is that it can be used to encrypt with UsernameTokens as well. this implementation can handle that as well and the code is commented out in the same menu item. it can also handle this for encrypted UsernameToken response messages.
for this one, the client uses the servers public certificate to encrypt the request.
the client code to make the call is at the menu item 'XmlEnc - asymm' of Menu 2. the web reference proxy class requires the WsExtension Attribute.
this sample works by sending a signed BinarySecurityToken as the request, and getting an encrypted response. on the server side you have to modify it to use Triple DES.
ISecurityTokenManager stm = SecurityTokenManager.GetSecurityTokenManagerByTokenType(WSTrust.TokenTypes.X509v3); X509SecurityTokenManager x509tm = stm as X509SecurityTokenManager; x509tm.DefaultSessionKeyAlgorithm = "TripleDES";
the client code for this call is at the 'XmlSig + XmlEnc' menu item of Menu 2. you will have to decorate the web reference proxy class with the WsExtension Attribute. the client passes its public certificate to the server to verify the signature, and encrypt the response. and the client signs and decrypts with the private key that is associated with the certificate. the certificates provided with WSE have been exported in the PFX format ... which CE cannot use. what i had to do was import the PFX format on my desktop, and then manually export the private key in the CryptoApi format (saved as Client Private.pvk). luckily, the CryptoApi format is the same on both the desktop and device. distributing the private key as a file is not recommended. in a real world app; it should live in the CertificateStore ... i just have not written that code yet. hopefully the next version of CE will support PFX.
this sample can be extended for the client to both sign and encrypt the request, as well as the server to sign and encrypt the response. this implementation does check that the Signature has not been tampered with for Response messages. it can also handle if the response has been signed 1st and then encrypted or vice versa.
this sample sends a request that is signed with an X509Token.
the client code to make the call is at the 'XmlSig - X509' menu item of Menu 2.
this sample receives a request to a router. the router checks for a SoapHeader, and then directs the request to Standard or Premium content appropriately. its referralCache.config uses localhost, so that needs to be changed. you also might change the SoapActor Attribute on both the Premium and Standard web services.
the client code to make the call is at the 'Cont.Based Router' menu item of Menu 3. the main thing it does is add the SoapHeader for Premium content. it also shows how to use the SoapActorUrl Property of the HeadersHandler.
uses a custom output Filter on the client to add a SoapHeader to the request. the web service has a custom input filter to process that Header. WSE Filters are similar to SoapExtensions, for allowing you to muck with the request and response messages.
for the client, i added a minimal Filter implementation. the code is in the 'Custom Filter' menu item of Menu 3. it shows how to register the custom output filter to be processed in the outgoing request. this also supports custom input Filters on the device. i need to add some priority to specify in which order filters need to be called.
this one requests a security context token from a token issuer, and then the client can use that security context token to make calls to the web service itself. the CustomBinaryTokenIssureCode project needs to have its web.config updated so that the serviceToken elements URI attribute does not point to localhost. then in the CustomBinaryTokenCode project, the BinaryToken class needs to have Aes128 switched to TripleDES. finally, in both the web service and token issuer code, it needs to be switched to using TripleDES as well ... using the same code from ResponseEncryption above.
the client code for this is in the 'Cust.BinSecToken' menu item of Menu 3. it uses a web reference proxy class to request the security context token. then the security context token returned is applied to the outgoing request to the actual web service.
this one is almost identical to CustomBinarySecurityToken. it requests a security context token from an issuer, and then uses that to call the web service. the issuer on the server side needs to have its web.config modified to change the URI attribute of the securityToken element to not be localhost.
for the client, the code lives in the 'Cust.XmlSecToken' menu item of Menu 3. it uses the same web reference proxy class to request the security token, and then calls the web service.
this sample has the client use a custom output filter to replay a message. the 1st time a request is sent it is cached. the 2nd time it is sent, instead of generating a new request, the one from the cache is sent. the server side will throw a SoapException if a replay is detected. the server side has a policyCache.config file that must be modified. the endpoint elements uri attribute must be changed from localhost.
the client uses my custom output filter mechanism to do the replay. if you call the same web service twice in a row then the server will exception (as expected). the code lives in the 'ReplayFilter' menu item of Menu 3.
this sample has both a client and server that run as console applications. you run the server and it starts listening for requests at port 8081. the client will then send a DimeRequest to that port. make sure the Server console app is running
the client uses the TcpClient class to make the same call. the only trick it has to do is wrap the SoapEnvelope in DIME. the code is in the 'TcpSync' menu item of Menu 4.
this sample is almost identical except it uses an asynchronous model. the server listens on port 8081. the client sends the request to port 8081, and listens for the response on 8082 in a separate thread. make sure the Server console app is running
the client code is in the 'TcpAsynch' menu item of Menu 4. this will not work on an emulator because the emulator cannot listen for requests. it must be run on an actual device that the desktop can send requests to.
this one is similar to the 2 above. the server side is now a SoapService, while the client side still uses System.Messaging. instead of using TCP, the transport is HTTP.
the client code is in the 'HttpSync' menu item of Menu 4. it uses the HttpWebRequest class to make the call. since it is HTTP, and not TCP, the SoapEnvelope does not have to be wrapped with DIME.
WSE 2.0 is not made to work with ASP.NET 1.1, and not Whidbey. the SecureConversation and DerivedKeyToken samples will fail if they are running under ASP.NET 2.0. the client will display an error to the effect of 'multiple roots found'. you need to change the virtual root directory settings to use ASP.NET 1.1, and it will fix this.
the client requests a security context token from an issuer. it then uses that security context token to make subsequent requests to the actual web service. the service must be modified to use TripleDES for SecurityContextTokens and UsernameTokens.
ISecurityTokenManager stm = SecurityTokenManager.GetSecurityTokenManagerByTokenType(WSTrust.TokenTypes.UsernameToken); UsernameTokenManager userTokM = stm as UsernameTokenManager; userTokM.DefaultKeyAlgorithm = "TripleDES"; ISecurityTokenManager stm2 = SecurityTokenManager.GetSecurityTokenManagerByTokenType(WSTrust.TokenTypes.SecurityContextToken); SecurityContextTokenManager secConTokM = stm2 as SecurityContextTokenManager; secConTokM.DefaultKeyAlgorithm = "TripleDES";
also, the full desktop client passes Entropy to the token issuer. the problem is that there is currently no way to make the token issuer return its Entropy using TripleDES. it will only use Aes128, thus the device client cannot pass Entropy. actually, it can pass Entropy, but there is no way for it to decrypt the returned response :( so ... dont pass Entropy. also note that the server side code for this sample does not work on some early versions of Whidbey. i have not tried with the Beta yet
the client code lives in the 'SecureConv v2 REL' menu item of Menu 4. it requests the security context token without Entropy, and then calls the web service.
this sample has the client request a security context token. it then derives 2 keys from that token. then it calls the web service, signing with one of the derived tokens and encrypting with the other. it must have the above algorithms set to use TripleDES, along with DerivedKeyTokens (below). this sample also had problems running on early versions of Whidbey.
ISecurityTokenManager stm3 = SecurityTokenManager.GetSecurityTokenManagerByTokenType(WSTrust.TokenTypes.DerivedKeyToken); DerivedKeyTokenManager derKeyTokM = stm3 as DerivedKeyTokenManager; derKeyTokM.DefaultKeyAlgorithm = "TripleDES";
the client code is in the 'DerivedKeyToken' menu item of Menu 4. currently, i have not implemented the code to handle DerivedKeyTokens returned from the web service itself.
i did not attempt this sample. it involves hosting the WSE pipeline within a console app. my current processing model could support this as well, but i dont think this is really worth doing until CF v2.0 provides XmlSerialization
i did not attempt this sample. it requires an ActiveDirectory setup or a stand-alone STS. if put on a domain with AD, then i would like to get this sample to work as well
although this code is now updated to work with WSE 2.0 RTM, parts of it will still work against WSE 1.0
it still works. my unit tests did not discover any breaking changes moving to WSE 2.0 SP1
i did most of my debugging by looking at the traces dumped on the server side. you can set this using the WSE Settings 2.0 menu item on a project, on the Diagnostics tab. make sure you enable message tracing, and that you set it to detailed error messages. the failure i see most of the time is that i sent the wrong encryption algorithm. e.g. the client sent TripleDES, but the server was expecting AES128. also sometimes represented as the passed key being the wrong length. e.g. the server was expecting 16 bytes of key data, and not 24
this article shows that WSE 2.0 Web Services are accessible to Compact Framework applications with a little work. i tested this on both Pocket PC 2003 and Smartphone 2003 hardware. both have CF service pack 1. it might also work on the 2003 emulators, but i've noticed that the crypto stuff will get hosed up every once in a while ... which does not happen on a device. the WS-Security stuff will NOT work on the PPC 2002 emulator because it does not come with the enhanced CryptoApi.
if you have questions, please ask them in the following newsgroups. i lurk in both regularly
the CF WSE 2.0 code is now a part of OpenNETCF.org. OpenNETCF is an open source project, supported by the Compact Framework MVPs and community, to provide functionality that is missing from the Compact Framework. the namespace is OpenNETCF.Web.Services2. it relies heavily on OpenNETCF.Security.Cryptography, which originated from the /spCrypt article
/cfWse2 Sample Source
you will want to look at the Sample code for this namespace. it contains all of the client-side code that is described above in the 'Samples' section
will keep updating this codebase as necessary. foreseeable updates include Plumbwork Orange, Visual Studio 2005, and Indigo. Plumbwork Orange is to WSE as OpenNETCF is to CF. it provides early WS-* implementations before they make their way into WSE (and ultimately Indigo). eventually i will make sure my WS-Eventing and WS-ReliableMessaging implementations will interop. also, i will update this code to work with Compact Framework v2.0 in Visual Studio 2005. one such change will be switching from the OpenNETCF.Security.Cryptography namespace to the MS implementation.
also want to keep making this easier to use. there are a handful of items that need the most work:
i'll be working on some different stuff for a while. later