Long story short:
My WCF clients should be able to provide both username and certificate to a service hosted in IIS, where I should use that information to validate requests using a custom policies.
Complete story:
I have the need to authenticate some WCF clients to verify if they can execute operations.
We have two kinds of clients: WPF applications and a web application. We would like to do the following:
In the implementation of the operations, I would like to verify if the certificate was provided (then I recognize the "super user"), otherwise fallback to username/password authentication.
Services are hosted in IIS 7 and we need to use NetTcpBinding. I was able to implement the username validation, but the problem is that the AuthorizationContext inspected by the service contains only identity information, and not the certificate. The following code is used on the client side to initialize the creation of channels (from a spike I'm using to test the solution):
var factory = new ChannelFactory<T>(this.Binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find<ClientCredentials>();
factory.Endpoint.Behaviors.Remove(defaultCredentials);
var loginCredentials = new ClientCredentials();
loginCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
loginCredentials.UserName.UserName = username;
loginCredentials.UserName.Password = password;
if (useCertificate)
{
loginCredentials.SetCertificate();
}
factory.Endpoint.Behaviors.Add(loginCredentials);
return factory.CreateChannel();
With the SetCertificate extension being implemented like this:
public static void SetCertificate(this ClientCredentials loginCredentials)
{
loginCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SecureWcfClient");
}
This is the configuration of the web application hosting the services:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="SecureBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="Test"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<clientCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType="AuthenticationProtectedService.Security.CertificateValidator, AuthenticationProtectedService.Security"/>
</clientCertificate>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="AuthenticationProtectedService.Security.UserNamePassValidator, AuthenticationProtectedService.Security" />
</serviceCredentials>
<serviceAuthorization serviceAuthorizationManagerType="AuthenticationProtectedService.Security.CertificateAuthorizationManager, AuthenticationProtectedService.Security"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding>
<security mode="None"/>
</binding>
<binding name="SecureNetTcp">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<service
name="AuthenticationProtectedService.Services.OneWayServiceB"
behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="wsHttpBinding"
contract="AuthenticationProtectedService.ServiceModel.IOneWayServiceB">
</endpoint>
</service>
<service
name="AuthenticationProtectedService.Services.DuplexServiceB" behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="netTcpBinding"
bindingConfiguration="SecureNetTcp"
contract="AuthenticationProtectedService.ServiceModel.IDuplexServiceB">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
Finally, this is the implementation of the custom authorization manager (I also tried with a custom certificate validator but the function was never run)
public class CertificateAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
if (!base.CheckAccessCore(operationContext))
{
return false;
}
string thumbprint = GetCertificateThumbprint(operationContext);
// I'd need to verify the thumbprint, but it is always null
return true;
}
private string GetCertificateThumbprint(OperationContext operationContext)
{
foreach (var claimSet in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Thumbprint, Rights.Identity))
{
string tb = BitConverter.ToString((byte[])claim.Resource);
tb = tb.Replace("-", "");
return tb;
}
}
return null;
}
}
I think that the problem could be in the clientCredentialType property of the nettcpbinding.Security.Message node on the service configuration, but I don't see the option to use both Certificate and Username withing the Message security.
Any help appreciated, thanks
Remark: a specific goal of the project is to have very low level impact on server setup and in general in the system, so also SSL should be avoided if possible.
try out this link http://msdn.microsoft.com/en-us/library/ms733099.aspx ...it might resolve your issue where in you can have different binding configuration for same binding type and associate the same to different endpoints as per your need.