WCF security and client error - c#

i have created a wcf service with Message security mode enabled this is my
service
[ServiceContract]
public interface IMessagingServices
{
[OperationContract]
string SendMessage(string from, string to, string message);
[OperationContract]
List<Message> GetMessages(string from, string to);
[OperationContract]
int DeleteMessages(int[] idList);
}
}
and this is it's config
<bindings>
<wsHttpBinding>
<binding name="wsBinding1">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="MessagingServices" behaviorConfiguration="SecureServiceBehavior" >
<endpoint address="" binding="wsHttpBinding" contract="IMessagingServices" bindingConfiguration="wsBinding1" />
<!--<endpoint address="mex" binding="mexHttpBinding" contract="IMetaDataExchange"/>-->
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="httpBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="SecureServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="KServic.local" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="AuthenticationHandler, mynamespace" />
</serviceCredentials>
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
everything seems to be fine , i can add service in add service reference in VS 2010 and the proxy is created successively but in client when try to call a service operation i get a error this is the client code
ServiceReference1.MessagingServicesClient mscClient = new MessagingServicesClient();
// mscClient.Open();
mscClient.ClientCredentials.UserName.UserName = "test";
mscClient.ClientCredentials.UserName.Password = "test";
mscClient.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByIssuerName, "KService.local");
// error is here
var msg = mscClient.SendMessage("rnd.test", "rnd.test", "Hello brother!");
mscClient.Close();
and this is error
The identity check failed for the outgoing message. The expected identity is 'identity(http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint)' for the 'http://192.168.100.24:16027/MessagingServices.svc' target endpoint.
what's wrong here ? and how can i fix this problem ?

You should set endpoint identity at client side when using message security.
The code you provided sets client credentials, not an endpoint identity.
See <identity> and <certificateReference> elements in WCF configuration schema, if you want to set up identity via .config file, or X509CertificateEndpointIdentity class, if you want to set up identity in code:
var certificate = ...; // load X509Certificate2 instance from the X509Store
var address = new EndpointAddress(uri, new X509CertificateEndpointIdentity(certificate));
Note, that service certificate must be validated before use at client side. For more information, see <authentication> of <serviceCertificate> element page.

Related

How to add user/pass authentication in WCF

I want to add some security to my WCF application service. I figured out how to add username/password authentication:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyBehavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Changer.Service.Validation.ServiceAuthenticator, Changer.Service"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="MyBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="Changer.Service.Request.RequestService" behaviorConfiguration="MyBehavior">
<endpoint address="/" binding="wsHttpBinding" contract="Changer.Service.Request.IRequestService" bindingConfiguration="MyBinding" />
</service>
</services>
</system.serviceModel>
This is my custom data validation:
public class ServiceAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
// Check the user name and password
if (userName != Authentication.Providers.Service.PasswordChanger.UserName ||
password != Authentication.Providers.Service.PasswordChanger.Password)
{
throw new System.IdentityModel.Tokens.SecurityTokenException("Unknown username or password.");
}
}
}
Unfortunelly I am getting error which is because I have no valid certificate. I tried following this tutorial:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-an-iis-hosted-wcf-service-with-ssl
But without success. It says, that certificate hosts is not matching site url I am visiting. On client side I am getting error:
Could not establish trust relationship for the SSL/TLS secure channel with authority 'foo'. The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel. The remote certificate is invalid according to the validation procedure
I can solve this by adding to my client app:
System.Net.ServicePointManager.ServerCertificateValidationCallback += delegate { return true; };
Which is basically not solving my problem.. What can I do with that? I just want to have simple user/pass authentication.
I decided to get rid off SSL, then my code changed to:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="PasswordChanger.Service.Validation.ServiceAuthenticator, PasswordChanger.Service"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name ="NewBinding">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
And after that I got this error:
Error: Cannot obtain Metadata from http://localhost:53705/R.svc If this is a Windows (R) Communication Foundation service to which you have access, please check that you have enabled metadata publishing at the specified address. For help enabling metadata publishing, please refer to the MSDN documentation at www.WS-Metadata Exchange Error URI: http://localhost:53705/R.svc Metadata contains a reference that cannot be resolved: 'http://localhost:53705/R.svc'. Content Type application/soap+xml; charset=utf-8 was not supported by service http://localhost:53705/R.svc. The client and service bindings may be mismatched. The remote server returned an error: (415) Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..HTTP GET Error URI: http://localhost:53705/R.svc The HTML document does not contain Web service discovery information.
So I decided to add services tag to my web.config next to bindings
<services>
<service name="PasswordChanger.Service.Request.RequestService" behaviorConfiguration="MyBehavior">
<endpoint address="/" binding="wsHttpBinding" contract="PasswordChanger.Service.Request.IRequestService" bindingConfiguration="NewBinding" />
</service>
And I got another error:
The service certificate is not provided. Specify a service certificate in ServiceCredentials.
Whether we use the message security or transport layer security mode, we all need to provide a certificate to ensure that the username/password authentication mode is secure.
I have made an example related transport security mode. we need provide a certificate to ensure that the service is hosted successfully.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication customUserNamePasswordValidatorType="WcfService1.CustUserNamePasswordVal,WcfService1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding>
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<protocolMapping>
<add binding="wsHttpBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
If we don’t have a certificate, we could generate a self-signed certificate by using IIS Server Certificate Tool.
then we add https binding in the IIS website binding module so that the WCF service is hosted successfully.
Custom Authentication class. Based on the actual situation, configure this authentication class in the configuration file above.
internal class CustUserNamePasswordVal : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "jack" || password != "123456")
{
throw new Exception("Username/Password is not correct");
}
}
}
<serviceCredentials>
<userNameAuthentication customUserNamePasswordValidatorType="WcfService1.CustUserNamePasswordVal,WcfService1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
Client.
//for validating the server certificate.
ServicePointManager.ServerCertificateValidationCallback += delegate
{
return true;
};
ServiceReference2.Service1Client client = new ServiceReference2.Service1Client();
client.ClientCredentials.UserName.UserName = "jack";
client.ClientCredentials.UserName.Password = "123456";
if we use the message security, we could set up the certificate by the following code.( Configure your actual certificate according to the actual situation)
<serviceCredentials>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" findValue="869f82bd848519ff8f35cbb6b667b34274c8dcfe"/>
<userNameAuthentication customUserNamePasswordValidatorType="WcfService1.CustUserNamePasswordVal,WcfService1" userNamePasswordValidationMode="Custom"/>
</serviceCredentials>
Refer to the below link.
WCF-TransportWithMessageCredential The HTTP request is unauthorized with client authentication scheme 'Anonymous'
WCF UserName & Password validation using wshttpbinding notworking
Feel free to let me know if there is anything I can help with.

Unable to use my WebService ad Connected Service

I created a webService with Basic Authentication (using this tutorial).
There is my web.config :
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="UsernameWithTransportCredentialOnly">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="RequestUserNameConfig">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MyInterface.CredentialsChecker,App_Code.CredentialsChecker"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="RequestUserNameConfig" name="MyInterface.MyService">
<endpoint
address="https://localhost:47336/MyService.svc"
binding="basicHttpBinding"
bindingConfiguration="UsernameWithTransportCredentialOnly"
name="BasicEndpoint"
contract="MyInterface.IMyService">
</endpoint>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="false" />
</system.serviceModel>
A folder App_Code contains file CredentialsChecker.cs with this code :
namespace MyInterface
{
public class CredentialsChecker : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
/* ... Some Code ... */
}
}
}
I created a project that I want to use to test my Web Service. But When I want to add the service as service reference, I got the error :
The provided URI scheme 'https' is invalid; expected 'http'.
Did I miss something in my web service ?
Your UsernameWithTransportCredentialOnly is of type basicHttpBinding. So you need to specify an endpoint that supports the binding. Either Change your address to http, or change the binding to wsHttp
<endpoint
address="http://localhost:47336/MyService.svc"
binding="basicHttpBinding"
bindingConfiguration="UsernameWithTransportCredentialOnly"
name="BasicEndpoint"
contract="MyInterface.IMyService">
</endpoint>

WCF / wsHttpBinding / Message Security - BadTokenRequest

So got an older WCF service / client I'm working on. Added a new (static) logging system to it, actually and now doing some load testing.
Getting some really annoying sporadic issues now - claiming "Secure channel cannot be opened because security negotiation with the remote endpoint has failed". I noticed I get a CommunicationException with a fault name of Sender and subcode of BadContextToken.
Weird thing is, I'll get 2-4 correct responses, then a flurry of these exceptions, then start getting good responses again.
This is my first real foray into WCF, and not loving it so far :)
Service web.config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</wsHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="ServiceBehavior" name="MyNamespace.MyService">
<endpoint address="" binding="wsHttpBinding" contract="MyNamespace.IMyService" bindingConfiguration="wsMessage">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="false" />
<serviceCredentials>
<serviceCertificate findValue="MyValue" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyNamespace.UserNamePassValidator, MyNamespace" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
And on the client side, the client is instantiated as such:
var binding = new WSHttpBinding();
binding.Name = "WSHttpBinding_IMyService";
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
var client = new MyService(binding, "http://myserver:8080/myapp/service.svc");
var endpointIdentity = new DnsEndpointIdentity("MyValue"); // Match the certificate name used by the server
client.Endpoint.Address = new EndpointAddress(new Uri("http://myserver:8080/myapp/service.svc"), endpointIdentity, client.Endpoint.Address.Headers);
var creds = client.ClientCredentials;
creds.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
creds.UserName.UserName = "myuser";
creds.UserName.Password = "mypassword";
string retVal = client.SendRequest(); // SendRequest == one of the methods on my IMyService, returns a string. This is also where I sporadically see my error when load testing.
I would appreciate any pointers to help me out with this WCF setup!
These might be useful additions to your web.config:
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="False" />
<serviceMetadata httpGetEnabled="True"/>
<serviceThrottling maxConcurrentCalls="20" maxConcurrentInstances="100"/>
</behavior>
</serviceBehaviors>
</behaviors>
<binding name="basicHttp" allowCookies="true" maxReceivedMessageSize="1048576" maxBufferSize="1048576" maxBufferPoolSize="1048576">
<readerQuotas maxDepth="32" maxArrayLength="1048576" maxStringContentLength="1048576"/>
</binding>
Usually this kind of "random" behaviour might depend on:
Timeouts (probably not your case, since you'd get a different exception)
Too many connections: if you client opens too many connections (and "forgets" to close them), you'll exceed the default allowed maximum (depending on context, it might be 10 connections).
You can act on this if you alter your web.config, editing maxConcurrentCalls and maxConcurrentInstances
Perhaps those errors are not random, but specific to some message; if so, that might be due to its size (i.e. it's too large): again, alter your web.config setting maxReceivedMessageSize, maxBufferSize, maxBufferPoolSize and readerQuotas
Of course you will get more info if you turn on WCF tracing.

WCF REST service not responding and issuing 500 error

I tried creating a WCF REST service in our existing ASP.NET application. However, when I try to browse to my service using the web browser, it says "This site can't be reached".
The URL I'm trying to hit is: http://localhost:81/ExternalServices/WS/Snap/REST/SnapService.svc
Also, when I try hitting it using a client tool, it gives me error 500.
// SnapService.svc
<% #ServiceHost Service = "DomainName.ProjectName.WebApp.ExternalServices.WS.Snap.REST.SnapService" Language="C#" %>
// ISnapService.cs
namespace DomainName.ProjectName.WebApp.ExternalServices.WS.Snap.REST
{
[ServiceContract]
public interface ISnapService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "UpdateLender")]
string UpdateLender(string userName, string password, string portfolioCode, string xmlRequest);
}
}
// SnapService.cs
namespace DomainName.ProjectName.WebApp.ExternalServices.WS.Snap.REST
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class SnapService : ISnapService
{
public string UpdateLender(string userName, string password, string portfolioCode, string xmlRequest)
{
// implementation here
return SnapServiceHelper.UpdateLender(userName, password, portfolioCode, xmlRequest);
}
}
}
// Web.config
<system.serviceModel>
<services>
<service name="DomainName.ProjectName.WebApp.ExternalServices.WS.Snap.REST.SnapService" behaviorConfiguration="defaultBehaviour">
<endpoint name="webHttpsBinding" address="" binding="webHttpBinding" contract="DomainName.ProjectName.WebApp.ExternalServices.WS.Snap.REST.ISnapService" behaviorConfiguration="webHttp" bindingConfiguration="webHttpTransportSecurity"/>
<endpoint name="mexHttpsBinding" address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="webHttpTransportSecurity">
<security mode="Transport"/>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="webHttp">
<webHttp helpEnabled="true" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="defaultBehaviour">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
EDIT: Enabled debugging to write to a log file. Here's the exception I get when I attempt to browse to the link above:
Operation 'UpdateLender' of contract 'ISnapService' specifies multiple request body parameters to be serialized without any wrapper elements. At most one body parameter can be serialized without wrapper elements. Either remove the extra body parameters or set the BodyStyle property on the WebGetAttribute/WebInvokeAttribute to Wrapped.

How do I programatically assign WCF ClientCredentials ServiceCertificate property?

I have a WCF service being hosted over https with a self-signed certificate. I'm having trouble programatically creating the binding: specifically the portion of the endpoint behavior.
My Service config looks like this:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="DisableServiceCertificateValidation">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None"
revocationMode="NoCheck" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="ContactEmail.Web.EmailService.customBinding0">
<binaryMessageEncoding />
<httpsTransport/>
</binding>
</customBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<service name="ContactEmail.Web.EmailService">
<endpoint address="https://xxxxxxxxxxxxxx/EmailService/EmailService.svc" binding="customBinding" bindingConfiguration="ContactEmail.Web.EmailService.customBinding0" contract="ContactEmail.Web.EmailService" behaviorConfiguration="DisableServiceCertificateValidation" />
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
And when I use the "Add Service Reference" feature, the generated client works as expected. Given that I call set up a Cert Validation Callback like this:
System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
However, rather than feed the service configuration through a config file on the client, I need to set it programmatically, because the call will be part of a commonly shared library. So, I'm trying to do this by providing my own parameters to the client constructor like:
var myClient = new EmailServiceClient(GetBinding(), new EndpointAddress(Strings.EmailServiceEndpointAddress));
In GetBinding(), I create CustomBinding with BindingElements like HttpsTransportBindingElement, BinaryMessageEncodingBindingElement and SecurityBindingElement.CreateSecureConversationBindingElement(SecurityBindingElement.CreateUserNameOverTransportBindingElement()).
Do you know how I can specify things like certificateValidationMode="None" and revocationMode="NoCheck" or if I'm doing anything wrong?
SecureConversation is implementation of WS-SecureConversation => advanced message level security where special security token is created during first call to the service (authenticated by the message security mode passed as parameter to the binding element creation) and this token is used to secure subsequent messages. This security also forms something know as security context or security session.
Your current binding in config file is not using SecureConversation so your binding defined in code is not compatible with your service.
You should have a Credentials property (of type ClientCredentials) (http://msdn.microsoft.com/en-us/library/ms733836.aspx) on your ClientBase (EmailServiceClient)...and that should have a ServiceCertificate property:
http://msdn.microsoft.com/en-us/library/system.servicemodel.description.clientcredentials.servicecertificate.aspx

Categories