Self hosted WCF + Custom UserNamePasswordValidator + Silverlight 4

1.9k views Asked by At

The code below suppose to run a self hosted with custom authentication WCF Service which needs to provide its services to a Silverlight 4 client (See code below).

The result is that the infamous clientaccesspolicy Security Error communication exception is thrown even though the clientaccesspolicy.xml is visible in browser and shows no SSL error. The clientaccesspolicy.xml breakpoint is not hit.

I realize I only need to specify the entry but I've tried various games with the clientaccesspolicy.xml which didnt work.

Your help is appreciated

1) This is the app.config and code for the service:

    <?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <system.serviceModel>
    <client />
    <bindings>
      <basicHttpBinding>
        <binding name="slBindingWithUserNamePassValidator">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </basicHttpBinding>
      <webHttpBinding>
        <binding name="capService" crossDomainScriptAccessEnabled="true">
          <security mode="Transport" />
        </binding>
        <binding name="capServiceNoSSL" crossDomainScriptAccessEnabled="true">
          <security mode="None" />
        </binding>
      </webHttpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="svcBehavior" name="WCF_Self_Hosted_UserName_Validator.Service1">
        <endpoint address="" binding="webHttpBinding" bindingConfiguration="capService" behaviorConfiguration="capServiceBehavior"
          contract="WCF_Self_Hosted_UserName_Validator.ICAPService" />
        <endpoint address="" binding="webHttpBinding" bindingConfiguration="capServiceNoSSL" behaviorConfiguration="capServiceBehavior"
          contract="WCF_Self_Hosted_UserName_Validator.ICAPService" />
        <endpoint address="MyCustomValidationService" binding="basicHttpBinding" bindingConfiguration="slBindingWithUserNamePassValidator"
          contract="WCF_Self_Hosted_UserName_Validator.IService1">
        </endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="https://(somesite):9999/" />
            <add baseAddress="http://(somesite):9998/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="svcBehavior">
          <serviceMetadata httpsGetEnabled="true" httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="capServiceBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

The code for the service:

Using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.IdentityModel.Selectors;
using System.IO;
using System.ServiceModel.Web;

namespace WCF_Self_Hosted_UserName_Validator
{
    class Program
    {
        static void Main(string[] args)
        {
            MyServiceHost host = new MyServiceHost(new Service1());
            host.Open();
            Console.WriteLine("Host open...");
            Console.ReadLine();
        }

    }
    public class MyServiceHost : ServiceHost
    {
        SecurityValidator _securityValidator = null;
        public MyServiceHost(IService1 svc) : base(svc)
        {
            Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
            _securityValidator = new SecurityValidator();
            Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = _securityValidator;
            Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "my-fqdn-valid-cert.dot.something");
         }
    }
    public class SecurityValidator : UserNamePasswordValidator
    {
        public SecurityValidator()
        {
        }
        public override void Validate(string userName, string password)
        {
            try
            {
                if (userName != "1" && password != "1")
                    throw new FaultException("auth error");
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string GetPrivateInfo();
    }
    [ServiceContract]
    public interface ICAPService
    {
        [OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
        Stream GetClientAccessPolicy();
    }
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class Service1 : IService1, ICAPService
    {
        public string GetPrivateInfo()
        {
            return "Some info " + DateTime.Now.ToShortTimeString();
        }
        public System.IO.Stream GetClientAccessPolicy()
        {
            WebOperationContext ctx = new WebOperationContext(OperationContext.Current);
            string txtCap = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<access-policy>
    <cross-domain-access>
        <policy>
            <allow-from http-request-headers=""*"">
                <domain uri=""*""/>
                <domain uri=""http://*""/>
                <domain uri=""https://*""/>
            </allow-from>
            <grant-to>
                <resource include-subpaths=""true"" path=""/""/>
            </grant-to>
        </policy>
    </cross-domain-access>
</access-policy>";
            WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
            MemoryStream response = new MemoryStream(Encoding.UTF8.GetBytes(txtCap));
            return response;
        }
    }
}

2) We have a CA signed SSL cert in the MY container of the LOCAL MACHINE and used netsh

    netsh http add sslcert ipport=0.0.0.0:9999 certhash=aabbcc_thumbprint
 appid={my_app_id_guid} clientcertnegotiation=enable

The above executes succesfully and the host loads properly and allows creating a new silverlight project.

3) The silverlight project is a just an new silveright project with add service reference and the following code:

namespace SilverlightApplication1
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            ServiceReference1.Service1Client c = new ServiceReference1.Service1Client();
            c.ClientCredentials.UserName.UserName = "1";
            c.ClientCredentials.UserName.Password = "1";
            c.GetPrivateInfoCompleted += new EventHandler<ServiceReference1.GetPrivateInfoCompletedEventArgs>(c_GetPrivateInfoCompleted);
            c.GetPrivateInfoAsync();
        }

        void c_GetPrivateInfoCompleted(object sender, ServiceReference1.GetPrivateInfoCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                this.Content = new TextBlock() { Text = e.Result };
            }
            else
            {
                this.Content = new TextBlock() { Text = e.Error.GetBaseException().Message };
            }
        }
    }
}

4) This is the ServiceReferences.ClientConfig generated

    <configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IService1" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="TransportWithMessageCredential" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://(TheAddress)/MyCustomValidationService"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
                contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
        </client>
    </system.serviceModel>
</configuration>
1

There are 1 answers

0
IdoFlatow On

Blockquote netsh http add sslcert ipport=0.0.0.0:9999 certhash=aabbcc_thumbprint appid={my_app_id_guid} clientcertnegotiation=enable

You've used the netsh with the clientcertnegotiation flag that means the server requires client certificate. When Silverlight calls the clientaccesspolicy, it does not send a client certificate, and that is why you get the exception.

If you don't need the client certificate remove this flag.

I'm not sure if SL is able to send a client certificate when fetching the clientaccesspolicy, but if your web page also access that site the browser should use the certificate you gave it. So yo ucan try adding a link to the secured site in your hosting html/aspx which will require you to select a certificate and then SL will use that certificate