Answered by:
WCF client certificate mapping

Question
-
I'm having a little difficulty in getting Client Certificate mapping to work with a WCF (basic, but doesn't matter which really) Receive Location.
The goal is to have the Party Resolver Pipeline component resolve the Client Certificate supplied via a web-service call. I have set the IIS directory to require SSL & Client Certificate; and set the Receive Location to Transport security with the credential type to Certificate.
Within the Party configuration, I have set the correct certificate against top-level Party.
I am expecting to see the PartyName context property to be set upon receipt of the message, but it is left blank.
I have tried enabling 'one-to-one client certificate mapping' in IIS to authenticate the client but this only really works if you disable all other authentication schemes in IIS, which the BizTalk Receive Location does not like as it expects it to be Anonymous.
It seems I'm struggling to get the client certificate to pass through to BizTalk - is there something obvious I'm missing here?
Thanks.
- Edited by Alastair Grant Tuesday, May 17, 2016 3:25 PM
Tuesday, May 17, 2016 3:25 PM
Answers
-
I agree- it would have been helpful in the BizTalk WCF adapter implementation to populate inbound credentials on the BizTalk message.
One way to achieve this would be to grab the WCF ServiceSecurityContext in your WCF Receive adapter via a custom WCF Message Inspector (exposed as a BehaviorExtension) and read the ServiceSecurityContext.Current.AuthorizationContext.ClaimSets[0]).X509Certificate property. Then add the X509Certificate.Thumbprint in your incoming message as a custom SOAP Header.
In your pipeline or any other downstream BizTalk code, you can then access that custom SOAP header and get at the certificate.
The second part of this article talks about something similar, albeit in a different context.
This is the WCF property that will be auto-populated by the WCF runtime if you are using certificate client credential type.
If you intend to go this route, this sample should be a good starting point - you can extend this with my earlier suggestions.
Thanks Arindam
- Edited by Arindam Paul RoyEditor Saturday, May 21, 2016 7:04 AM
- Marked as answer by Alastair Grant Wednesday, May 25, 2016 8:39 AM
Saturday, May 21, 2016 6:40 AMModerator -
If somebody else wants to do this, here are some snippets to get you underway:
/// <summary> /// WCF Message inspector to hunt out those client certificates and pass onto BizTalk /// </summary> /// <remarks> /// Fails silently to allow anonymous activity to continue, change as required. /// Author: Alastair Grant /// </remarks> public class ClientCertificatePromotionInspector : System.ServiceModel.Dispatcher.IDispatchMessageInspector { #region IDispatchMessageInspector Members public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext) { try { if(OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets.Count > 0) { System.Security.Cryptography.X509Certificates.X509Certificate2 clientCert = ((X509CertificateClaimSet)OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets[0]).X509Certificate; request.Headers.Add(MessageHeader.CreateHeader("ClientCertificate", "http://internal.namespace/wcf", clientCert.Thumbprint)); } } catch { } return null; } public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { } #endregion } /// <summary> /// WCF Behaviour wrapper for <see cref="ClientCertificatePromotionInspector" /> /// </summary> public class ClientCertificatePromotionBehaviour : System.ServiceModel.Description.IServiceBehavior { #region IServiceBehavior Members public void AddBindingParameters(System.ServiceModel.Description.ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<System.ServiceModel.Description.ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } /// <summary> /// Adds the <see cref="ClientCertificatePromotionInspector"/> to available endpoint message inspectors /// </summary> /// <param name="serviceDescription"></param> /// <param name="serviceHostBase"></param> public void ApplyDispatchBehavior(System.ServiceModel.Description.ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach(ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) { foreach(EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new ClientCertificatePromotionInspector()); } } } public void Validate(System.ServiceModel.Description.ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } #endregion } /// <summary> /// Provides information for machine.config /// </summary> public class ClientCertificatePromotionBehaviourElement : System.ServiceModel.Configuration.BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(ClientCertificatePromotionBehaviour); } } protected override object CreateBehavior() { return new ClientCertificatePromotionBehaviour(); } }
Register ClientCertificatePromptionBehaviourElement in the machine.config for it to be visible in the WCF behaviour list in BizTalk.
WCF headers get wrapped with XML tags when going into the BizTalk console, so you need a little decode pipeline component to extract it, I won't paste the whole class, but this is the IComponent bit:
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg) { string cert = (string)pInMsg.Context.Read("ClientCertificate", "http://internal.namespace/wcf"); XmlDocument xDoc = new XmlDocument(); xDoc.LoadXml(cert); pInMsg.Context.Write("SignatureCertificate", "http://schemas.microsoft.com/BizTalk/2003/system-properties", xDoc.DocumentElement.InnerText); return pInMsg; }
Used a dirty XmlDocument for demo purposes, you all know how to improve that as required.If this is helpful or answers your question - please mark accordingly.
Because I get points for it which gives my life purpose (also, it helps other people find answers quickly)- Proposed as answer by Rachit SikroriaModerator Wednesday, May 25, 2016 4:06 PM
- Marked as answer by Alastair Grant Thursday, March 2, 2017 11:24 PM
Wednesday, May 25, 2016 3:48 PM
All replies
-
Hi
>Is the SignatureCertificate property getting populated in your input message? Refer this. If the adapter is not doing this, you would need the MIME decoder component to do this.
"If the party is resolved, the SourcePartyID for that party is placed in the context of the message as the OriginatorPID of the message if the host process running the pipeline is marked as Authentication Trusted by the pipeline. If the party resolution cannot be completed by using the certificate, the OriginatorPID value on the message is stamped with "s-1-5-7", which is the SID of an anonymous user." https://msdn.microsoft.com/en-us/library/aa560645.aspx
Check what value is getting promoted in OriginatorPID property. You may have to mark your IsolatedHost as Authentication Trusted.
> Do you see anything in the SourcePartyID property for the input message? If yes, party resolution is working fine. You will have to do a lookup to read the PartyName from this id.
>Is the client sending a signed message, and are you being able to validate the signature?
>Is the Resolve Party By Certificate property set to True in the Party Resolution component?
Thanks Arindam
- Edited by Arindam Paul RoyEditor Tuesday, May 17, 2016 4:15 PM
Tuesday, May 17, 2016 3:39 PMModerator -
The default certificate party resolution doesn't not work with the WCF adapter. It works with the SMIME decoder component.
You need to write a custom pipeline component to put the BTS.SignatureCertificate context property into the message context for the party resolution component to work - or write your own party resolution component.
Refer: Party Resolution using certificate
Rachit Sikroria (Microsoft Azure MVP)
- Edited by Rachit SikroriaModerator Tuesday, May 17, 2016 4:33 PM updated link
- Proposed as answer by Rachit SikroriaModerator Wednesday, May 25, 2016 4:01 PM
Tuesday, May 17, 2016 4:20 PMModerator -
I cannot seem to access the Client Certificate information from IIS in the pipeline, I find nothing remotely related in the context.
Looking at the WCF Adapter documentation, the ClientCertificate property is only used by the Send adapter for communicating out. I want a similar property during receive so I can authorise inbound connections.
It seems strange that there is an option to set the Transport client credential type to 'Certificate' but it doesn't actually do anything.
- Edited by Alastair Grant Friday, May 20, 2016 1:35 PM
Friday, May 20, 2016 1:35 PM -
I agree- it would have been helpful in the BizTalk WCF adapter implementation to populate inbound credentials on the BizTalk message.
One way to achieve this would be to grab the WCF ServiceSecurityContext in your WCF Receive adapter via a custom WCF Message Inspector (exposed as a BehaviorExtension) and read the ServiceSecurityContext.Current.AuthorizationContext.ClaimSets[0]).X509Certificate property. Then add the X509Certificate.Thumbprint in your incoming message as a custom SOAP Header.
In your pipeline or any other downstream BizTalk code, you can then access that custom SOAP header and get at the certificate.
The second part of this article talks about something similar, albeit in a different context.
This is the WCF property that will be auto-populated by the WCF runtime if you are using certificate client credential type.
If you intend to go this route, this sample should be a good starting point - you can extend this with my earlier suggestions.
Thanks Arindam
- Edited by Arindam Paul RoyEditor Saturday, May 21, 2016 7:04 AM
- Marked as answer by Alastair Grant Wednesday, May 25, 2016 8:39 AM
Saturday, May 21, 2016 6:40 AMModerator -
Cracking suggestion, thanks for the links, I think this is going to be the only way to achieve this.
- Edited by Alastair Grant Wednesday, May 25, 2016 8:39 AM
Wednesday, May 25, 2016 8:38 AM -
If somebody else wants to do this, here are some snippets to get you underway:
/// <summary> /// WCF Message inspector to hunt out those client certificates and pass onto BizTalk /// </summary> /// <remarks> /// Fails silently to allow anonymous activity to continue, change as required. /// Author: Alastair Grant /// </remarks> public class ClientCertificatePromotionInspector : System.ServiceModel.Dispatcher.IDispatchMessageInspector { #region IDispatchMessageInspector Members public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext) { try { if(OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets.Count > 0) { System.Security.Cryptography.X509Certificates.X509Certificate2 clientCert = ((X509CertificateClaimSet)OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets[0]).X509Certificate; request.Headers.Add(MessageHeader.CreateHeader("ClientCertificate", "http://internal.namespace/wcf", clientCert.Thumbprint)); } } catch { } return null; } public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { } #endregion } /// <summary> /// WCF Behaviour wrapper for <see cref="ClientCertificatePromotionInspector" /> /// </summary> public class ClientCertificatePromotionBehaviour : System.ServiceModel.Description.IServiceBehavior { #region IServiceBehavior Members public void AddBindingParameters(System.ServiceModel.Description.ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<System.ServiceModel.Description.ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } /// <summary> /// Adds the <see cref="ClientCertificatePromotionInspector"/> to available endpoint message inspectors /// </summary> /// <param name="serviceDescription"></param> /// <param name="serviceHostBase"></param> public void ApplyDispatchBehavior(System.ServiceModel.Description.ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach(ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) { foreach(EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new ClientCertificatePromotionInspector()); } } } public void Validate(System.ServiceModel.Description.ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } #endregion } /// <summary> /// Provides information for machine.config /// </summary> public class ClientCertificatePromotionBehaviourElement : System.ServiceModel.Configuration.BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(ClientCertificatePromotionBehaviour); } } protected override object CreateBehavior() { return new ClientCertificatePromotionBehaviour(); } }
Register ClientCertificatePromptionBehaviourElement in the machine.config for it to be visible in the WCF behaviour list in BizTalk.
WCF headers get wrapped with XML tags when going into the BizTalk console, so you need a little decode pipeline component to extract it, I won't paste the whole class, but this is the IComponent bit:
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg) { string cert = (string)pInMsg.Context.Read("ClientCertificate", "http://internal.namespace/wcf"); XmlDocument xDoc = new XmlDocument(); xDoc.LoadXml(cert); pInMsg.Context.Write("SignatureCertificate", "http://schemas.microsoft.com/BizTalk/2003/system-properties", xDoc.DocumentElement.InnerText); return pInMsg; }
Used a dirty XmlDocument for demo purposes, you all know how to improve that as required.If this is helpful or answers your question - please mark accordingly.
Because I get points for it which gives my life purpose (also, it helps other people find answers quickly)- Proposed as answer by Rachit SikroriaModerator Wednesday, May 25, 2016 4:06 PM
- Marked as answer by Alastair Grant Thursday, March 2, 2017 11:24 PM
Wednesday, May 25, 2016 3:48 PM