Adjusting HttpClientHandler in WCF in .NET 6
Our company has a lot of legacy code and one of them is WCF clients. I was personally against using it a long time ago, but people who were responsible for that part went with it anyway.
The WCF was easy to use but there was a lot of complexity underneath and REST API was already popular when we decided to use WCF and it was tricky to do RESET with WCF. These were reasons I was against using it. But anyway we are using it right now and there are problems when we are migrating it to .NET 6.
One of these issues was a certificate check. We would like our customers to use self-signed certificates for testing and while it is possible to trust self-signed certificates most of our customers found it too inconvenient for testing and as a result, I will explain how to do it in WCF for .NET 6.
But before I will explain how to do it, I would like to state that you need to ignore certificates very carefully because it opens man-in-the-middle attacks. Somebody can intercept traffic and provide your application their certificate and your application will blindly accept it.
Also, I would like to state that validating a certificate is hard (as is all security in general) and please do not trust the first block of code you find somewhere on Stackoverflow or a similar website. Many developers including developers from Microsoft made a lot of mistakes in this function, so please be careful.
Anyway, in .NET Framework it was very simple. All you need to do is to assign your handler to the static property ServicePointManager.ServerCertificateValidationCallback. After you assign this callback any communication with any server will use it to validate the server certificate.
But it does not work in .NET and this callback is rarely called. Instead in .NET 6, most communication is done via HttpClient and it uses its own property called HttpClientHandler.ServerCertificateCustomValidationCallback to validate the certificate.
But WCF clients usually don’t have access to HttpClientHandler instance that will be used to do all communication. Here is code that I found and that works quite well:
public class CustomHttpClientBehavior : IEndpointBehavior
{
private readonly static Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> callback = (request, certificate, chain, sslPolicyErrors) =>
{
// You code is here
};
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// Copied from https://github.com/dotnet/wcf/issues/4214
bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(GetHttpMessageHandler));
}
private HttpMessageHandler GetHttpMessageHandler(HttpClientHandler handler)
{
if (handler.ServerCertificateCustomValidationCallback != callback)
{
try
{
handler.ServerCertificateCustomValidationCallback = callback;
}
catch
{
// Suppress any exceptions because exception is raised if handler were already used
}
}
return handler;
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
// Not needed
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// Not needed
}
public void Validate(ServiceEndpoint endpoint)
{
// Not needed
}
}
And then add this line when you have a client:
client.Endpoint.EndpointBehaviors.Add(new CustomHttpClientBehavior());
You can do it in each place or in the constructor of this object. We have a special function that adds default behaviors and I added it there.
I hope it helps someone.