Thursday, 26 March 2009

Securing Anonymous web services with Certificates

We are currently working with a client who requires two BizTalk web services to talk to each other, however because they are in different locations there is no common account we can use to secure the communication. Below shows an example of the setup:

BizTalk Server 1:

image

This orchestration has an input port (which accepts an xml file) – this message is then passed to a request reply port (which will call a webservice hosted on BizTalk server 2). The response is then passed to the output port (which is saved to an xml file).

BizTalk server 2:

image

This orchestration is a simple webservice which accepts a message, does something with the contents and returns the new message.

This posting assume you have two orchestrations already talking to each other using wcf without any authentication

Ok that’s the scenario defined – lets get onto the more interesting part!

Configuring BizTalk server 1 (The WCF consumer):

Since we are going to be securing the webservice with certificates lets start by generating the client certificate. Run the following code on the server which will be connecting to the webservice (BizTalk server 1 in my case) in a command prompt:

makecert -r -pe -n "CN=ClientCertName" -b 01/01/2005 -e 01/01/2100 -sky exchange -ss my

To view the certificate type mmc into command prompt, then go to file add/remove snap in, and select Certificates. You should be given an option to select select who you will be managing the certificates for – select “My user account”


image


Now if you look in the Personal folder you should see your certificate. We will need to install this same certificate in a few other places so right click on it and select export. Ensure you include the private key (you actually don't need the private key on the BizTalk server hosting the webservice, but for this demo I have included it).


Now we need to install this certificate in two different locations. On BizTalk server 1 we need to install it under the user account running the host instance. To do this open up a command prompt and type:

runas /user:DOMAINNAME\USERNAME mmc

then type the password for the user. In the management console again select the certificate add in. You can then import the certificate by right clicking on personal and selecting import.


Now we can configure this server to use the certificate you have added. Go into the send port you have generated and ensure you used the type wcf-wshttp – then click configure. For the address modify the url to use Https, then go to the security tab. Here you want to change the security mode to Transport with transport client type: certificate. Then in the client certificate panel click browse. Here you should be able to select the certificate you have just generated.


That’s the first server configured.


Configuring Server 2 (The WCF Provider):


On the second server go to the receive location and select your WCF port. Click configure and go to Security. Set the security mode to Transport, and the client credential type to Certificate.


image


Now we need to configure IIS to host the ssl web service.


Firstly we will need to get another certificate to allow ssl to occur – so right click on the web site and go to properties, then Directory Security and click server certificate. Click the “Create a new certificate” and next followed by “Prepare the request now, but send it later” (You may be able to click send the request immediately to an online certification authority depending on your setup) Fill in all the details ensuring the field named “Common Name” is the dns name used to connect to this machine from Server 1. This will generate a text file which you can then give to a certificate authority to get a valid ssl certificate (this will have to be a certificate authority trusted by Server one – so possible a public authority like VeriSign).


Once you have your certificate you should be able to add it by re-running the above wizard.


Now what we need to do is install the client certificate generated on server 1 – ensure this is installed into the Local Computer certificate store so IIS can access it. Remember for security this certificate should not really include the private key.


After you have added your certificate click the edit button shown below


image


Then select the following “Require Secure Channel”, “Require Client Certificates” and “Enable Certificate Trust List”, then click New...


This Wizard will allow you to specify the client certificates which can be used to access the webservice. Click Add from store then select the client certificate installed above. Click next, give it a friendly name, next and Finish. Then select the certificate list:


image


You will probably have to restart IIS now before this works correctly (and I had to restart IIS every time I changed the certificate trust list).


And thats it! Transport security with client certificates DONE.




Here are some error messages you might get and how to fix them:


The adapter failed to transmit message going to send port "SendPort1" with URL "https://ServerName/TestWebserviceCommunication/TestWebserviceCommunication_ProcessMessage_SendAndRecieve.svc". It will be retransmitted after the retry interval specified for this Send Port. Details:"System.InvalidOperationException: Cannot find the X.509 certificate using the following search criteria: StoreName 'My', StoreLocation 'CurrentUser', FindType 'FindByThumbprint', FindValue '23D68B9999C21FC93702D259736DD0CC1C752B61'.
at System.ServiceModel.Security.SecurityUtils.GetCertificateFromStoreCore(StoreName storeName, StoreLocation storeLocation, X509FindType findType, Object findValue, EndpointAddress target, Boolean throwIfMultipleOrNoMatch)
at System.ServiceModel.Security.SecurityUtils.GetCertificateFromStore(StoreName storeName, StoreLocation storeLocation, X509FindType findType, Object findValue, EndpointAddress target)
at System.ServiceModel.Security.X509CertificateInitiatorClientCredential.SetCertificate(StoreLocation storeLocation, StoreName storeName, X509FindType findType, Object findValue)
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2.ApplyClientCertificate(ClientCredentials clientCredentials)
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.

This error is because your client certificate is stored in your cert store, but not the cert store used by the biztalk host instance. Look at the step above showing how to run mmc as another user to fix this problem.


The adapter failed to transmit message going to send port "SendPort1" with URL "https://ServerName/TestWebserviceCommunication/TestWebserviceCommunication_ProcessMessage_SendAndRecieve.svc". It will be retransmitted after the retry interval specified for this Send Port. Details:"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.
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result)
--- End of inner exception stack trace ---

Server stack trace:
at System.ServiceModel.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndRequest(IAsyncResult result)

Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at System.ServiceModel.Channels.IRequestChannel.EndRequest(IAsyncResult result)
at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2.RequestCallback(IAsyncResult result)"
.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

This error is because the service you have used was not trusted on the Server 2, ensure you have the correct client certificate selected in IIS.

2 comments:

angsram said...

Thank you Ross, I have been stuck for a couple of days on the issue of "Cannot find X.509 certificate". runas /user:DOMAINNAME\USERNAME mmc did the trick.. We dont come across this error in development machines. Its such an important step in configuration expecially when we deploy the app in test or prod environments.
Thanks a ton.

vibhu said...

Please let me know where to run the command:

runas /user:DOMAINNAME\USERNAME mmc
in dot net 2.0.