The Belgian government offers
a list of basic web services that hospitals and/or healthcare providers can use for building healthcare applications.
These SOAP based web services are written in Java and make heavy use of security. There's an
excellent WCF library available that makes it more easy to use these web services from .net.
We received a question from one of our clients if one of those basic services (Trusted Timestamping Service (TTS)) could be used from within a Biztalk orchestration. This post is about all the steps we took and all the problems we solved to be able to support this. Unfortunately I'll have to disappoint you that as far as we tried it is not possible. We got very far but the technical limitations of Biztalk nailed us. If you want to find out which
'marvelous' journey we undertook: although my Biztalk knowledge equals to null, I'll be your guide during this wonderful trip of WCF, .net and Biztalk.
First we needed to find out if the library supports calling the Trusted Timestamping Service (TTS), the basic service we need to call, as it is not
listed as tested. We've made a small test application that calls the eHealth TTS on their test environment.
Sidenote: for the application to work you need a valid installed eHealth certificate and it needs be correctly configured in your WCF endpointBehavior configuration.
The use of the library involves adding a custom binding extension to the app.config of the application.
<extensions>
<bindingextensions>
<add name="TimeStampBinding" type="Siemens.EHealth.Client.Sso.Sts.Configuration.StsBindingCollectionElement, Siemens.EHealth.Client"/>
</bindingExtensions>
</extensions>
Problem 1: When running the test application we got the following exception:
Configuration binding extension 'system.serviceModel/bindings/TimeStampBinding' could not be found. Verify that this binding extension is properly registered in system.serviceModel/extensions/bindingExtensions and that it is spelled correctly.
Solution: You could change your machine.config and add the binding there, but that's not advisable. Or you could just
add an empty bindings element to the system.serviceModel configuration:
<system.serviceModel>
<bindings/>
<extensions>
<bindingextensions>
<add name="TimeStampBinding" type="Siemens.EHealth.Client.Sso.Sts.Configuration.StsBindingCollectionElement, Siemens.EHealth.Client"/>
</bindingExtensions>
</extensions>
<behaviors>
<endpointbehaviors>
<behavior name="TimeStampBehavior">
<clientcredentials type="Siemens.EHealth.Client.Sso.WA.OptClientCredentials, Siemens.EHealth.Client">
<clientcertificate storeLocation="CurrentUser" storeName="My" x509FindType="FindByThumbprint" findValue="c0c3438821b96b24cc94dad318d0cbaef31b5c34"/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
After modifying the app.config with the above configuration we moved on to problem 2.
Problem 2: Our first try resulted in the following exception:
<env:Fault xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<faultcode>wsse:FailedCheck
<faultstring>weblogic.xml.crypto.encrypt.api.XMLEncryptionException: Unable to resolve encryption key for weblogic.xml.crypto.encrypt.api.EncryptedType{keyInfo=null, cipherData=CipherValue: JVO/dyAV5eIPIDGahQwBsI/CuqmFzBzfGe9MH9MM2bNDDpyGxa9ZXboX/3co8BzlDZY6F+b7R4DDaLXg0XZzO3gFiM7v1EaqVjuQcbiujiTdAWTVi921WMeO4to4Q9Px3ez6tBtYdIlU//WV+JRonL6NIBN66vs42E46PqOJIRIl3hx3ai589yBgcdAJTH3CCqVDXY3XcotWGtRpa4eu9K5fNxFs6o2Ez1gaeVAGLYOZwnRHVzg++Y+7V9dSS+UzwFEuVTLSvzqSg0p+mfgzlmAPtqTxDVro2cM3aJdJTgra86aEb9fp2q2jAE9BVXT2JDqa1yXWT26SajuyJqZAeABFBo0CmiVeDS/iSUQwvZaSc5g94G+6BQOs+JA7xEIAlWEihmLZyXT9LjhNF5CS/ZLBJqGWzRWcSByeIXrDn5sFtobLKP2o8P1hHxkzht4RQrEFsPs58jyUYd9wFLh/WLmWK6dQ2YjUsBkJI3A+bpK32FfhUagcpP22NmS7kJbJWylbvpInXftEnGGDTyrmD7DGYAMyoCH3X2jluXAXckHZpN0aBRgg4aQIGMYvSyZGHnmnXo8r4FFegH0nSHIIJaUMiFLKtRBmaChMtwyqajwT49AcUCfEvROlM6L2zBjkn7BMYTosaeZHBKTBzW1L8aT6lw5iKi/ISw2FFD96qSAY9pnScaPOdftoPbJd7mpV9Y3U8I2gO9JTLapsu5TP/C5sdRanLeKPriHKwSm/3DAfWKxxcFt1hDqswY9pFNuym4mXlXwru2ZKbRtqqwa0JRF7FuHCFBYK1aM8JtcBTrE2hx4aA2JpLTQ4JlaSgVk7uFwW7GDiO5iKSHCJPGTimHqGfzJnIRTJqGIhhk2p1MWmh30hMjkxIHv7L4uiOIytLZPgUCuEzdhMxZsMMD5H2bDv1QOp6oWI6s+cdo2GD9l9fdtbtYR8QUvfuSU410Lsin2iIZwigH9Z16YtmNMRlWdBg7ZRNJQF582qNiOnv6L7nar5uwuP0SXwWEALo47HCTcXRO2hV5rGn+r4BS8rg20gh+La30Icd60NIphn9/xAyPk48cedq7aY0t1Lj5w1Zbtd7NIcfr4p5vPqAs20NUTs+Fw2DYkyGPNZuVhFLnxXVYuiXsr9IAsN8F54BqEFRBHbDRbQ0CWOXm+p/98cAlPCWj9I6c9prDu6zGMw9p3jfg5MQ3c5nB0z9X7cUGhV/0cIQ2h/LCxXUGHJUPYVRxsMJoDz5zUxv9v2UM1GlqN1tvW7tfq0MyojdYRkrMBvfuYoMSyG2Xssa+fLhgN7SLMYjknjynbPdt5pFnL8yQs4cqgD743i3KSMDhnNmMAnNi1IlDe8jdGoL+sUQxEtXw==, id='_2', mimeType='null', encoding='null', encryptionMethod=EncryptionMethod: algorithm = http://www.w3.org/2001/04/xmlenc#aes256-cbc;}
</faultstring>
</env:Fault>
Solution: Googling this exception didn't deliver anything useful, but looking at
the documentation of the library again we realized we had overlooked something important. You have to manually set the protectionlevel parameter on the ServiceContractAttribute of the generated service reference. When this was modified we received a valid response from the TTS.
At this point we had a working test application that was targeted at .net framework 4.0. Our next step was to
configure a WCF endpoint in Biztalk to use the custom binding extension.
Problem 3: You can use a WCF endpoint in Biztalk, but on Biztalk 2006 R2 you are limited to .net framework 3.5 because
.net framework 4.0 is not supported on this version.
Solution: The
WCF library we are using is targeted for .net framework 4.0. Download the source, set the target framework to 3.5 and compile! Oops, doesn't work because:
Problem 4: In .net framework 3.5 there is a property missing on SecurityBindingElement, namely EnableUnsecuredResponse. It is a boolean value that enables WCF to send secured messages and receive unsecured responses, and to send unsecured messages and receive secured responses. We tried to comment out the setter of the property, but this resulted in following exception:
Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.
Solution: There is a hotfix available that enables this property for .net framework 3.5 SP1. But you need to know which one you need and it is by request only. The biztalk server we are going to deploy to is running on a Windows Server 2003 32 bit so we requested
this one. We also needed this on a development machine where we were to compile the test application. This has a Windows 7 OS, but we couldn't install the appropriate hotfix, only receiving 'this hotfix is not valid for this OS' from the failing installer. It turns out that the specific KB was rolled up in Windows 7 SP1 and thus already on the development machine.
After removing the System.ServiceModel reference and re-adding it again, and uncommenting the setter for the EnableUnsecuredResponse property, we could compile our test application targetting .net framework 3.5 and run it on the development machine. Next up was running this on our test biztalk server that had the same setup as the one with our client.
Problem 5: The hotfix for Windows Server 2003 consists of 2 KB's where the first one got installed and the second one (the one we need) doesn't get installed without failing the installation. We noticed this because the version number of System.ServiceModel assembly in the GAC didn't change. After '
dotpeeking' the assembly we were sure this wasn't the updated assembly because it still was missing the EnableUnsecuredResponse property.
Solution: We managed to extract the assemblies from the hotfix and install them ourselves in the GAC. And yes! our test application targetting .net framework 3.5 worked, but this - manually updating the .net framework with assemblies from a hotfix you can only get by request - felt very uncomfortable!
So we were back at problem 3: Configuring a Biztalk WCF endpoint with the EHI library targetting .net framework 3.5.
Solution: see also here
- You need to install the EHI assembly in the GAC for Biztalk
- You need to modify machine.config so Biztalk can find the binding extension (see problem 1)
- you need the assembly somewhere in a path where Biztalk can find it (something like C:\Program Files\Biztalk\). I have no idea why we needed to put it in the GAC and also somewhere else. This was something we had to figure out after the setup succeeded, but as we never got there, this remains unsolved.
We finally had a configured WCF endpoint that didn't throw any errors! Next step was calling the TTS through the endpoint.
Problem 6: Bam, a new exception:
The adapter failed to transmit message going to send port "WcfSendPort_TimeStampAuthority_timestampauthorityPort" with URL "https://wwwacc.ehealth.fgov.be/timestampauthority_1_5/timestampauthority". It will be retransmitted after the retry interval specified for this Send Port. Details:"System.InvalidOperationException: The ClientCredentials cannot be added to the binding parameters because the binding parameters already contains a SecurityCredentialsManager 'System.ServiceModel.Description.ClientCredentials'. If you are configuring custom credentials for the channel, please first remove any existing ClientCredentials from the behaviors collection before adding the custom credential.
at System.ServiceModel.Description.ClientCredentials.System.ServiceModel.Description.IEndpointBehavior.AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters)
at System.ServiceModel.Description.DispatcherBuilder.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection parameters)
at System.ServiceModel.Description.DispatcherBuilder.BuildProxyBehavior(ServiceEndpoint serviceEndpoint, BindingParameterCollection& parameters)
at System.ServiceModel.Channels.ServiceChannelFactory.BuildChannelFactory(ServiceEndpoint serviceEndpoint)
at System.ServiceModel.ChannelFactory.CreateFactory()
at System.ServiceModel.ChannelFactory.OnOpening()
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open()
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2.CreateChannelFactory[TChannel](IBaseMessage bizTalkMessage)
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2.InitializeValues(IBaseMessage message)
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2..ctor(IBaseMessage message, WcfTransmitter`2 transmitter)
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfTransmitter`2.GetClientFromCache(String spid, IBaseMessage message)
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfAsyncBatch`2.BatchWorker(List`1 messages)".
For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
Apparently
Biztalk adds its own client credentials to the generated WCF client. From that blogpost there seems no way to get around this, but the first comment on the blog gives a solution to overcome this.
Solution: Create a custom BehaviorExtension that removes ClientCredentials.
public class BiztalkClientCredentialsBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public void Validate(ServiceEndpoint endpoint) {}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
ClientCredentials clientCredentials = bindingParameters.Find();
bindingParameters.Remove(clientCredentials);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) {}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) {}
protected override object CreateBehavior()
{
return new BiztalkClientCredentialsBehavior();
}
public override Type BehaviorType
{
get
{
return typeof(BiztalkClientCredentialsBehavior);
}
}
}
And configure it:
<system.serviceModel>
<bindings/>
<extensions>
<behaviorextensions>
<add name="BiztalkClientCredentials" type="Siemens.EHealth.Client.Sso.Sts.Configuration.BiztalkClientCredentialsBehavior, Siemens.EHealth.Client, Version=1.0.0.9, Culture=neutral, PublicKeyToken=3dd81f6b7de27543"/>
</behaviorExtensions>
<bindingextensions>
<add name="TimeStampBinding" type="Siemens.EHealth.Client.Sso.Sts.Configuration.StsBindingCollectionElement, Siemens.EHealth.Client"/>
</bindingExtensions>
</extensions>
<behaviors>
<endpointbehaviors>
<behavior name="TimeStampBehavior">
<biztalkclientcredentials />
<clientcredentials type="Siemens.EHealth.Client.Sso.WA.OptClientCredentials, Siemens.EHealth.Client">
<clientcertificate storeLocation="CurrentUser" storeName="My" x509FindType="FindByThumbprint" findValue="c0c3438821b96b24cc94dad318d0cbaef31b5c34"/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Attention: Contrary to the BindingExtension configuration, the BehaviorExtension type must be the full name of the assembly (with correct version and publickeytoken).
So with this BehaviorExtension the automatically added ClientCredentials were removed and the ones we specify in the config were added. Haha, nothing will stop us now!
Problem 7: After doing a new call with the use of the WCF endpoint we bumped against another exception:
In short: Unable to resolve encryption key for weblogic.xml.crypto.encrypt.api.EncryptedType
We encountered this exception before (see problem 2) and thus all we needed to do was add the protectionLevel parameter to the ServiceContract attribute.
Solution: We didn't find any solution for this. As far as I know Biztalk dynamically generates the WCF proxy code and we didn't find a way to inject, intercept, change or modify it.
So there we are, probably at the last step to completing the complete setup, but we thought that with every problem we solved, thinking 'this will be the last one'.
We finally decided on creating 1 Windows Service configured with Quartz.net for scheduling. Easy to install, update, configure, no assemblies in GAC, no manual .net framework updates and no complicated Biztalk orchestrations.