WCF Client Certificate Validation + Windows Authentication - c#

I've successfully created a WCF service which validates the incoming client certificate against the chain configured in IIS. However, as this is only a security mechanism to support authentication, I also need the Windows user calling my WCF service to handle authorization.
Normally when extracting the Windows User, you would do it like this
ServiceSecurityContext.Current.WindowsIdentity.Name
When my service is configured with security mode TransportWithMessageCredentials, the PrimaryIdentity in the ServiceSecurityContext will return the certificate's SubjectName and the WindowsIdentity will be empty.
To look at the client configuration, I've specified the WsHttpBinding like this
private static Binding GetHttpsBinding()
{
var binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
return binding;
}
The client certificate is added like to the proxy client like this:
private static void ApplyClientCertificate(HelloServiceClient client)
{
client.ClientCredentials.ClientCertificate.SetCertificate(
storeLocation: StoreLocation.CurrentUser,
storeName: StoreName.My,
findType: X509FindType.FindBySubjectName,
findValue: "ClientCertificatesTest"
);
}
Switching the two ClientCredentialType values so the binding looks like this
private static Binding GetHttpsBinding()
{
var binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
return binding;
}
will work for extracting the Windows Credentials as described above, but when presenting an invalid certificate or no certificate at all are also accepted! Therefore the authentication requirement is not fulfilled. I can also add that when configured this way my implementation of X509CertificateValidator on the server-side will not trigger, hence my suspicion that the client certificate is not added.
Surely there must be some way to add a client certificate for authentication and add Windows Credentials to handle authorization in WCF? Is there any other way that I can add the certificate than adding it to the client credentials?
Thanks in advance!

So the answer to this question will be to create your own CustomBinding to get both Windows Credentails and Certificate validation.

With Web Service references you could present both a client certificate and Windows authentication credentials, so it's strange that this isn't available out of the box for WCF?
Did you implement the custom binding or have any links of examples of getting this working?
UPDATE: here's the solution to create a custom binding to get both Windows Authentication and Client Certificates.
http://david-homer.blogspot.com/2021/05/using-net-wcf-basichttpbinding-to.html
Thanks,
Dave

Related

WCF client HTTPS request exception

I am in the process of building a WCF client for a SOAP HTTPS webservice in .Net Core 2.1.
The service provider has supplied a .key and a .cert file which I have converted to a .p12 file using openssl. By adding this to a keystore I am able, through SoapUI, to successfully sent a request to the webservice (no other authentication than the certificate is required).
To do the same operation in .Net Core I have added a Connected Service to my project through the WCF wizard in Visual Studio. This service is based on the supplied service contract (WSDL file). I have then installed the .p12 certificate locally on my PC and I am using the following code to make the request. "MyService" is the connected service.
var binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
var endpoint = new EndpointAddress("https://x.x.x.x:8300/MyService.asmx");
var channelFactory = new ChannelFactory<MyService>(binding, endpoint);
channelFactory.Credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
channelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck,
TrustedStoreLocation = StoreLocation.LocalMachine
};
channelFactory.Credentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindByIssuerName,
"xxxxxxxxxxxxxxxxxxxxxxxxxx");
var service = channelFactory.CreateChannel();
ExecuteResponse response = service.Execute(new ExecuteRequest());
When running this code I am getting the following error:
System.ServiceModel.Security.MessageSecurityException: 'The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Negotiate'.'
The strange thing is that I am allowed to make the request if I use the HttpClientHandler which tells me that there must be a mismatch between the underlying structure of the two implementations.
Anyone who knows how I can fix this error?
The certificate might just be used to establish the trust relationship between the client-side and the server-side.
For making a successful call to the service, we should keep the binding type between the client-side and the server-side consistent. Therefore, I would like to know the automatically generated client-side configuration by Adding service reference, please post the System.servicemodel section located in the appconfig of the client project.
If the server authenticates the client-side with a certificate, the error typically indicates the trust relationship has not established yet between the client-side and the server-side.
On the client-side, we should install the server certificate in the LocalCA. On the server-side, we should install the client certificate in the LocalCA certificate store.
Feel free to let me know if the problem still exists.

How to fix "The HTTP request was forbidden with client authentication scheme 'Anonymous'"

I'm having some issues implementing a client that talks to a WCF service. It's a WCF hosted by another company so I don't have access to its code. I used the Connected Service provider tool in Visual Studio to generate the client code so that I could make requests and everything works fine on my local machine. I am having an issue on our development environment where I receive the following error if I try to make a request with the client:
The HTTP request was forbidden with client authentication scheme 'Anonymous'
I've been looking at the client code (it's a lot of code) which is generated by the Provider tool and I think it may have something to do with the following block of code.
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
return result;
This more linked to firewall rules within corporate network.
I had same issue using non authorized proxy but got resolved secured proxy with ntlm ClientCredentialType
This error typically indicates that the WCF server authenticates the client-side with a certificate. The error will occur when the trust relationship between the server and the client have not been established yet.
In general, we need to provide client credential to be authenticated by the server so that be able to call the service. As for what kind of credentials need to be provided, it depends on the binding information on the server-side.
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
Namely, the above errors have indicated that the server authenticates the client with a certificate.
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
About authenticating the client with a certificate, you could refer to the below link for details.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-with-certificate-authentication
Feel free to let me know if there is anything I can help with.
Thanks for all the suggestions. This was actually just caused by a firewall rule that was setup within our organisation. Once that was removed the code worked as expected.
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
Security is provided using HTTPS. The service must be configured with SSL certificates. The SOAP message is protected as a whole using HTTPS. The service is authenticated by the client using the service's SSL certificate. The client authentication is controlled through the ClientCredentialType.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.basichttpsecuritymode?view=netframework-4.8

Can client credentials be read from within service implementation?

I am hosting a soap webservice via an instance iHost of ServiceHost; authentication is configured as
HttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
iHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode
= UserNamePasswordValidationMode.Custom;
iHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator
= new CustomValidator();
The hosting itself works as desired, however I also would like to access the client credentials from within the hosted service itself. Can this be achieved with the current authentication settings or is it impossible?
Found the answer with the help of a coworker. Username can be accessed via OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name; the question can be seen as a duplicate of this question.

How to create a WCF client using digest authentication to third-party system

I have been given a wdsl for an external system (not .Net-based), and I have used svcutil to create a client/proxy for it. The external unit requires digest authentication for me to talk to it, and supports both http and https.
A couple of questions:
There are no certificates involved. Will using https cause problems in that case?
I know I can specify transport level digest authentication like this:
var binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Digest;
However, how do I go about creating the credentials and using them with my binding/proxy?
I can easily find a lot of information online on creating WCF services, but implementation of clients towards non-.Net based services... not so much.
Thanks for any insight!

WCF windows authentication security error

i have some code that tries impersonate the callers windows security settings and then connect to another WCF service on a different machine
WindowsIdentity callerWindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity;
using (callerWindowsIdentity.Impersonate())
{
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
EndpointAddress endpoint = new EndpointAddress(new Uri("net.tcp://serverName:9990/TestService1"));
ChannelFactory<WCFTest.ConsoleHost.IService1> channel = new ChannelFactory<WCFTest.ConsoleHost.IService1>(binding, endpoint);
WCFTest.ConsoleHost.IService1 service = channel.CreateChannel();
return service.PrintMessage(msg);
}
But I get the error:
"the caller was not authenticated by the service"
System.ServiceModel .... The request for security token could not be satisfied because authentication failed ...
The credentials I am trying to impersonate are valide windows credential for the box the service is on.
Any ideas why?
In order to support your scenario, you need to have an understanding of how Protocol Transition and Constrained Delegation work. You will need to configure both Active Directory and your WCF service endpoint(s) to support this. Note the use of the Service Principal Name (SPN). Take a look at the following link and see if they help you. The article has a sample to demonstrate the complete end-to-end configuration required to make this work.
How To: Impersonate the Original Caller in WCF Calling from a Web Application
Agree with marc_s this is the double-hop problem.
You need to get the windows authentication all the way through, therefore:
The request must be made in the context of a windows users
IIS must be configured to use windows authentication
Web.config must be set up for windows authentication with impersonate = true
The user that your application pool is running as, must be allowed to impersonate a user. This is the usual place where the double-hop problem occurs.
There is a right called "Impersonate a client after authentication"
http://blogs.technet.com/askperf/archive/2007/10/16/wmi-troubleshooting-impersonation-rights.aspx
Impersonation from you service to the next is a tricky issue, known as "double-hop" issue.
I don't have a final answer for that (I typically avoid it by using an explicit service account for the service that needs to call another service).
BUT: you should definitely check out the WCF Security Guidance on CodePlex and search for "Impersonation" - there are quite a few articles there that explain all the ins and outs of impersonating an original caller and why it's tricky.
Marc
If you are sure you have the credentials right on both hops, the next thing that could be causing the issue is the lack of the EndpointDnsIdentity being set on the endpoint.
DnsEndpointIdentity identity = new DnsEndpointIdentity("localhost"); // localhost is default. Change if your service uses a different value in the service's config.
Uri uri = new Uri("net.tcp://serverName:9990/TestService1");
endpoint = new EndpointAddress(uri, identity, new AddressHeaderCollection());

Categories

Resources