views:

150

answers:

1

Edit: When I execute test the service with a client I get a response. I should not get a response. What should happen is CertificateValidator.Validate() should throw an exception because there is no certificate being sent. I KNOW it is not being called because if I get it to throw an exception without making any tests it still doesn't. So, I am positive the configuration is wrong

EDIT: I think some code is missing in this post, from the XML file. Don't know how to get it in. Importantly is the config for the authentication which is in there.

There is a lot of code here but here goes. First an explanation, I have a client which works. My service works. Now I want to get some custom authentication working. I don't see why it isn't, when I test it there should be an error or no response because a certificate is not being sent, but there isn't which is wrong.

edit: Sorry, to add, there are references to all the required files etc.

Here is the configuration for the service. The bit for the reference to the custom class is at the bottom, it looks nearly identical to what is on the MSDN site.

<?xml version="1.0"?>
<configuration>

<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
  <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
    <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere" />
      <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
      <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
      <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
    </sectionGroup>
  </sectionGroup>
</sectionGroup>
</configSections>
<appSettings/>
<connectionStrings/>
 <system.web>
  <compilation debug="true">
  <assemblies>
    <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
    <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
    <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
  </assemblies>
 </compilation>
 <!--
    The <authentication> section enables configuration 
    of the security authentication mode used by 
    ASP.NET to identify an incoming user. 
-->
<authentication mode="Windows" />
<!--
    The <customErrors> section enables configuration 
    of what to do if/when an unhandled error occurs 
    during the execution of a request. Specifically, 
    it enables developers to configure html error pages 
    to be displayed in place of a error stack trace.

    <customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
     <error statusCode="403" redirect="NoAccess.htm" />
     <error statusCode="404" redirect="FileNotFound.htm" />
    </customErrors>
-->
<pages>
  <controls>
    <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  </controls>
</pages>

<httpHandlers>
  <remove verb="*" path="*.asmx"/>
  <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>
</httpHandlers>
<httpModules>
  <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpModules>
</system.web>
<system.codedom>
<compilers>
  <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4"
            type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
    <providerOption name="CompilerVersion" value="v3.5"/>
    <providerOption name="WarnAsError" value="false"/>
  </compiler>
</compilers>
</system.codedom>
 <!-- 
  The system.webServer section is required for running ASP.NET AJAX under Internet
  Information Services 7.0.  It is not necessary for previous version of IIS.
 -->
 <system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules>
  <remove name="ScriptModule" />
  <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</modules>
 <handlers>
  <remove name="WebServiceHandlerFactory-Integrated"/>
  <remove name="ScriptHandlerFactory" />
  <remove name="ScriptHandlerFactoryAppServices" />
  <remove name="ScriptResource" />
  <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode"
       type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode"
       type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</handlers>
</system.webServer>
<runtime>
<assemblyBinding appliesTo="v2.0.05727" xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
    <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
    <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
    <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
  </dependentAssembly>
  </assemblyBinding>
 </runtime>
 <system.serviceModel>
 <services>
  <service behaviorConfiguration="HelloWorldWCF2.Service1Behavior"
    name="HelloWorldWCF2.HelloWorld">
    <endpoint address="" binding="wsHttpBinding" contract="HelloWorldWCF2.IHelloWorld">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>
<behaviors>
  <serviceBehaviors>
    <behavior name="HelloWorldWCF2.Service1Behavior">
      <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
      <serviceMetadata httpGetEnabled="true"/>
      <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="false"/>
    </behavior>
  </serviceBehaviors>

    <endpointBehaviors>
        <behavior name="HelloWorldWCF2.HelloWorldBehaviour">
            <clientCredentials>
                <serviceCertificate>
                    <authentication certificateValidationMode="Custom"
                           customCertificateValidatorType="CertificateValidator.X509Validator, client"/>
                </serviceCertificate>
            </clientCredentials>
        </behavior>
    </endpointBehaviors>

</behaviors>
  </system.serviceModel>
</configuration>

Next are my service classes:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace HelloWorldWCF2
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the     interface name "IService1" in both code and config file together.
[ServiceContract]
public interface IHelloWorld
{

    [OperationContract]
    string GetData();

    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);

    // TODO: Add your service operations here
}


// Use a data contract as illustrated in the sample below to add composite types to service operations.
[DataContract]
public class CompositeType
{
    bool boolValue = true;
    string stringValue = "Hello ";

    [DataMember]
    public bool BoolValue
    {
        get { return boolValue; }
        set { boolValue = value; }
    }

    [DataMember]
    public string StringValue
    {
        get { return stringValue; }
        set { stringValue = value; }
    }
  }
}

And:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using BusinessLogic;
using CertificateValidator;

namespace HelloWorldWCF2
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class     name "Service1" in code, svc and config file together.
public class HelloWorld : IHelloWorld
{
    CertificateValidator.X509Validator X509Val = new CertificateValidator.X509Validator();
    public string GetData()
    {
        return new Hello().hello();
    }

    public CompositeType GetDataUsingDataContract(CompositeType composite)
    {
        if (composite == null)
        {
            throw new ArgumentNullException("composite");
        }
        if (composite.BoolValue)
        {
            composite.StringValue += "Suffix";
        }
        return composite;
    }
 }
}

Finally is my CertificateValidation class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;

namespace CertificateValidator
{
  public class X509Validator : X509CertificateValidator
{
    private string[] arrThumbPrints = new string[2];
    protected string[] GetTrustedThumbprints()
    {
        return arrThumbPrints;
    }

        public override void Validate(X509Certificate2 certificate)
        {
            // create chain and set validation options
            X509Chain chain = new X509Chain();
            SetValidationSettings(chain);

            // check if cert is valid and chains up to a trusted CA
            if (!chain.Build(certificate))
            {
                throw new SecurityTokenValidationException("Client certificate is not valid");
            }

            // check if cert is from our trusted list
            if (!IsTrusted(chain, GetTrustedThumbprints()))
            {
                throw new SecurityTokenValidationException("Client certificate is not trusted");
            }
        }

        protected virtual void SetValidationSettings(X509Chain chain)
        {
            // override to use non-default validation settings
        }

        protected virtual ValidationMode ValidationMode
        {
            get { return ValidationMode.Issuer; }
        }

        protected virtual bool IsTrusted(X509Chain chain, string[] trustedThumbprints)
        {
            int depth = 0;

            if (ValidationMode == ValidationMode.EndCertificate)
            {
                // only check the end certificate
                return CheckThumbprint(chain.ChainElements[0].Certificate, trustedThumbprints);
            }
            else
            {
                // check the rest of the chain
                foreach (X509ChainElement element in chain.ChainElements)
                {
                    if (++depth == 1)
                    {
                        continue;
                    }

                    if (CheckThumbprint(element.Certificate, trustedThumbprints))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private bool CheckThumbprint(X509Certificate2 certificate, string[] trustedThumbprints)
        {
            foreach (string thumbprint in trustedThumbprints)
            {
                if (string.Equals(certificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        protected void DumpDiagnostics(X509Chain chain, X509Certificate2 certificate)
        {
            Console.WriteLine("Subject: {0}", certificate.Subject);
            Console.WriteLine("Issuer : {0}", certificate.Issuer);

            Console.WriteLine("Status: {0}", chain.ChainStatus.Length);
            foreach (X509ChainStatus status in chain.ChainStatus)
            {
                Console.WriteLine("{0} \\ {1}",
                    status.Status,
                    status.StatusInformation);
            }

            foreach (X509ChainElement element in chain.ChainElements)
            {
                Console.WriteLine(element.Information);
            }
        }
    }
  }
A: 

Hello,

I see two problems in your configuration.

  1. You are using default WSHttpBinding. Default setting uses message security with Windows credentials. This security setting does not require certificates so it doesn't use them at all. You have to define new WSHttpBinding with client credential type set to certificate and reference that setting in your endpoint by using endpointConfiguration attribute.
  2. I'm not sure which certificate are you going to validate. Your configuration describes validation of server certificate which should be used on a client to validate negotiated service credentials but you are using it in the service configuration = it will not be used. You have to define service behavior and use serviceCredentials\clientCertificate\authentication to specify validation of client certificate.

Best regards, Ladislav

Ladislav Mrnka