Showing posts with label WCF. Show all posts
Showing posts with label WCF. Show all posts

Tuesday, 25 March 2014

ID1038: The AudienceRestrictionCondition was not valid because the specified Audience is not present in AudienceUris.

After installing a new environment today, I got this error in one of our services:
ID1038: The AudienceRestrictionCondition was not valid because the specified Audience is not present in AudienceUris.
I thought: that's a new one.

This was a very simple one to solve, though, it turns out that the AudienceUri needs to match the text, casing included, of the uri in the federationmetadata file, otherwise you get this error, who knew?

Relevant extract from FederationMetadata.xml file:

entityID="https://testserver.test.local/UAT/Scheduling.Service/Scheduler.svc"

Relevant extract from the web.config file:

<microsoft.identityModel>
 <service name="SchedulingService.Scheduler">
  <audienceUris>
   <add value="https://testserver.test.local/uat/Scheduling.Service/Scheduler.svc />
 </audienceUris>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
 <trustedIssuers>
 <add thumbprint="abcdef790108485dee20eeca19c8132e11abcdef"  name="http://adfs.test.local/adfs/services/trust" />
 </trustedIssuers>
 </issuerNameRegistry>
 <certificateValidation certificateValidationMode="None" />
 </service>
</microsoft.identityModel>

As you can see uat is not capitalized in the audienceUri which is enough to cause the issue.

Friday, 12 July 2013

Hosting a RESTful JSON WCF Service from a console app or a windows service.

I've been working for a while on an application, too long to explain what it actually does, but the bottom line is that it requires, or at least it could benefit from having, a RESTful WCF service hosted on both http and https endpoints.

I toyed with the idea of doing the application in Python as it uses some Python code but I decided to stick with what I knew as I wanted to finish it quickly. At any rate, here is the code:

This is simply shown as an example of how it could be done, if you follow it, your end point will be listening on http://<hostname>/store/ and can be invoked by simply navigating to it like this:

http://<hostname>/store?page=url 

In order for the application to listen on https you will need to have a valid certificate on your certificate store. The subject name should match the hostname of the machine running this application and then this should work:

https://<hostname>/store?page=url 

First the interface:

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

namespace WcfJsonRestService
{
    [ServiceContract]
    public interface IStore
    {
        [OperationContract]
        bool Store(string item);
    }

}
Then the class implementing the interface:

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

namespace WcfJsonRestService
{
   
    public class Store : IStore
    {
        [WebInvoke(Method = "GET",
                    ResponseFormat = WebMessageFormat.Json,
                    UriTemplate = "store?page={item}")]
        public bool Store(string item)
        {
            //do stuff here

            return true;
        }

    }
}
And finally a Console application that hosts the service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace WcfJsonRestService
{
    class Program
    {
        static void Main(string[] args)
        {

            try
            {
                using (ServiceHost host = new ServiceHost(typeof(RESTful)))
                {

                    AddServiceEndPoint(host, "https://{0}/store", true, "change me");
                    AddServiceEndPoint(host, "http://{0}/store", false);

                    host.Open();

                    Console.WriteLine("Service host running......");
                    Console.WriteLine("Press Any key at any time to exit...");

                    Console.Read();

                    host.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                Console.Read();
            }


        }

        private static void AddServiceEndPoint(ServiceHost host, string url, bool useSSLTLS, string certSubjectName="")
        {
            string addressHttp = String.Format(url,
                System.Net.Dns.GetHostEntry("").HostName);


            WebHttpBinding binding;

            if (useSSLTLS)
            {

                binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
                binding.HostNameComparisonMode = HostNameComparisonMode.WeakWildcard;
                binding.CrossDomainScriptAccessEnabled = true;
            }
            else
            {
                binding = new WebHttpBinding(WebHttpSecurityMode.None);
                binding.CrossDomainScriptAccessEnabled = true;
            }

            // You must create an array of URI objects to have a base address.
            Uri uri = new Uri(addressHttp);
            Uri[] baseAddresses = new Uri[] { uri };

            WebHttpBehavior behaviour = new WebHttpBehavior();
            // Add an endpoint to the service. Insert the thumbprint of an X.509 
            // certificate found on your computer. 
            host.AddServiceEndpoint(typeof(IRESTful), binding, uri).EndpointBehaviors.Add(behaviour);

            if (useSSLTLS)
            {
                host.Credentials.ServiceCertificate.SetCertificate(
                    StoreLocation.LocalMachine,
                    StoreName.My,
                    X509FindType.FindBySubjectName,
                    certSubjectName);
            }
        }
    }
}

Alternatively, the WCF service can be hosted by a Windows service. Code behind for windows service here:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using StoreAndConvert.WCFService;
using System.Security.Cryptography.X509Certificates;
using System.Configuration;


namespace StoreAndConvert.WindowsService
{
    public partial class Store : ServiceBase
    {

        string certSubjectName = string.Empty;

        ServiceHost host;

        public Store()
        {
            InitializeComponent();
        }


        protected override void OnStart(string[] args)
        {
            try
            {
                //Debugger.Launch();
                certSubjectName = ConfigurationManager.AppSettings["CertificateSubjectName"];

                host = new ServiceHost(typeof(StoreUrls));

                AddServiceEndPoint(host, "https://{0}/storeurl", true, certSubjectName);
                AddServiceEndPoint(host, "http://{0}/storeurl", false);

                host.Open();

                Trace.WriteLine("Service host running......");
                Trace.WriteLine("Listening on");

                foreach (ServiceEndpoint sep in host.Description.Endpoints)
                {
                    Trace.WriteLine(string.Format("endpoint: {0} - BindingType: {1}",
                        sep.Address, sep.Binding.Name));
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            }

        }

        protected override void OnStop()
        {
            try
            {
                if (host != null)
                {
                    host.Close();
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            }
        }

        private void AddServiceEndPoint(ServiceHost host, string url, bool useSSLTLS, string certSubjectName = "")
        {
            string addressHttp = String.Format(url,
                System.Net.Dns.GetHostEntry("").HostName);

            WebHttpBinding binding;

            if (useSSLTLS)
            {
                binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
                binding.HostNameComparisonMode = HostNameComparisonMode.WeakWildcard;
                binding.CrossDomainScriptAccessEnabled = true;
            }
            else
            {
                binding = new WebHttpBinding(WebHttpSecurityMode.None);
                binding.CrossDomainScriptAccessEnabled = true;
            }

            // You must create an array of URI objects to have a base address.
            Uri uri = new Uri(addressHttp);
            Uri[] baseAddresses = new Uri[] { uri };

            WebHttpBehavior behaviour = new WebHttpBehavior();
            // Add an endpoint to the service. Insert the thumbprint of an X.509 
            // certificate found on your computer. 
            host.AddServiceEndpoint(typeof(IStoreUrls), binding, uri).EndpointBehaviors.Add(behaviour);

            if (useSSLTLS)
            {
                host.Credentials.ServiceCertificate.SetCertificate(
                    StoreLocation.LocalMachine,
                    StoreName.My,
                    X509FindType.FindBySubjectName,
                    certSubjectName);
            }
        }
    }
}

Monday, 3 December 2012

SSL Mutual (Two-way) Authentication for WCF services in IIS 7.5 using client certificates

In a previous post, I described  how to configure SSL client Authentication in IIS 7.5 using a One-to-One Mapping.

In this post I describe how to set up a client authenticated WCF web service.

Firstly, there are three pre-requisites.
  1. Server Certificate from trusted CA.
  2. Client Certificate from trusted CA.
  3. WCF service deployed to a website that has been configured to use SSL, see this post for details on how to configure a website to use SSL.
I would suggest following my excellent series on CAs, which starts here, but alas it's mostly oriented for IIS 6, so it's not exactly terribly useful, it does create a CA which is the basis but not much more. Similarly, this post details the usage of makecert to create self-signed certificates but again it's geared towards IIS 6, the certificate generation commands will work though. You can also use openSSL, details here to create self-signed certificates.

In any case, the first step is to ensure that the website is set to require SSL and client certificates:


The second step is to enable Anonymous Authentication. This might sound like a bad idea and to a certain extent it is, using a one to one mapping is a better idea, but I've not got that working yet. What is achieved with this configuration is that any user that has a client certificate from a trusted CA will be be able to use the WCF service.



Third step is to change the configuration file of the WCF service to this (Use your own name and Contract):

  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="TransportSecurity">
          <security mode="Transport">
            <transport clientCredentialType="Certificate"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service name="API.Service" behaviorConfiguration="behaviour">
        <endpoint name="AnEndPoint" address="" binding="wsHttpBinding"
               contract="API.IService"  bindingConfiguration="TransportSecurity">
        </endpoint>
       </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="behaviour">
          <serviceMetadata   httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

And finally the client that consumes the web service needs to have this on the configuration file:

<system.serviceModel> 
 <bindings> 
  <wsHttpBinding> 
  <binding name="TransportSecurity"> 
   <security mode="Transport"> 
    <transport clientCredentialType="Certificate" /> 
   </security> 
  </binding> 
  </wsHttpBinding> 
 </bindings> 
 <client> 
  <endpoint address="https://fqdn/service.svc" behaviorConfiguration="behaviour" 
  binding="wsHttpBinding" bindingConfiguration="TransportSecurity" 
  contract="API.IService" name="TransportSecurity" /> 
 </client> 
 <behaviors> 
  <endpointBehaviors> 
   <behavior name="behaviour"> 
    <clientCredentials> 
     <clientCertificate findValue="577aeb8b603af8453b80c55e9ac4abdfd4cf9c6c" 
     storeLocation="CurrentUser" x509FindType="FindByThumbprint" />
    </clientCredentials> 
   </behavior> 
  </endpointBehaviors> 
 </behaviors> 
</system.serviceModel>

Note that you can also use a browser to test this.