Search code examples
.netwcfsslproxywsfederationhttpbinding

WCF WS2007FederationHttpBinding with HTTPS


I'm encountering the following error when trying to connect to WCF API which is behind a HTTPS Proxy :

The server certificate with name 'CN=hostname' failed identity verification because its thumbprint ('X') does not match the one specified in the endpoint identity ('Y'). As a result, the current HTTPS request has failed. Please update the endpoint identity used on the client or the certificate used by the server.

Where X is the thumbprint of the certificate used by the proxy and Y the thumbprint of the certificate used by the service

The problem is that I managed to get a token from the STS but I cannot perform any web service call after that.

I reproduced the problem on my PC by using a local SSL proxy, the certificate used on the proxy is trusted on my PC. Everything is ok when using HTTP.

I searched a solution for several days now and I noticed this KB article which is close to my problem but not applicable anymore (I'm running the sample and the server in .Net 4.5) : https://support.microsoft.com/en-us/kb/2564823

What am i missing ?

Here is the code :

class Program
{
    static void Main(string[] args)
    {
        Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us");

        string serverUrl = ConfigurationManager.AppSettings["ServerURL"];
        GenericXmlSecurityToken token = GetToken(serverUrl);

        Console.WriteLine("Token Received");
        Console.WriteLine(token);

        TestServiceClient client = CreateClient(serverUrl, token);

        try
        {
            client.SearchSomething();

            Console.WriteLine("SearchSomething succeeded");
        }
        catch (Exception e)
        {
            Console.WriteLine("SearchSomething failed :" + e);
        }

        Console.ReadLine();

    }

    private static TestServiceClient CreateClient(string serverUrl, GenericXmlSecurityToken token)
    {
        var binding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.Message)
        {
            MaxReceivedMessageSize = int.MaxValue,
            MaxBufferPoolSize = int.MaxValue
        };

        binding.Security.Message.EstablishSecurityContext = false;
        binding.Security.Message.IssuedTokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1";
        binding.Security.Message.NegotiateServiceCredential = false;

        binding.ReaderQuotas.MaxDepth = int.MaxValue;
        binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
        binding.ReaderQuotas.MaxArrayLength = int.MaxValue;
        binding.ReaderQuotas.MaxBytesPerRead = int.MaxValue;
        binding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;

        var uri = new Uri(serverUrl + "Test.Service/Test.Service.svc");

        var identity = new X509CertificateEndpointIdentity(new X509Certificate2(ConfigurationManager.AppSettings["ServiceCertificate"], ConfigurationManager.AppSettings["ServiceCertificatePassword"]));

        var client = new TestServiceClient(binding, new EndpointAddress(uri, identity));
        client.ClientCredentials.SupportInteractive = false;

        var customBinding = new CustomBinding();
        var bindingElements = binding.CreateBindingElements();

        if (serverUrl.Contains("https"))
        {
            bindingElements.Remove<HttpTransportBindingElement>();

            bindingElements.Add(new HttpsTransportBindingElement() { MaxReceivedMessageSize = int.MaxValue });
        }
        customBinding.Elements.AddRange(bindingElements.ToArray());

        client.Endpoint.Binding = customBinding;

        var clientCredentials = new SamlClientCredentials(token, client.ClientCredentials);
        client.Endpoint.Behaviors.Remove<ClientCredentials>();
        client.Endpoint.Behaviors.Add(clientCredentials);

        return client;
    }

    private static GenericXmlSecurityToken GetToken(string serverUrl)
    {
        string username = ConfigurationManager.AppSettings["Username"];
        string password = ConfigurationManager.AppSettings["Password"];
        string identityDnsName = ConfigurationManager.AppSettings["IdentityDnsName"];
        string ClientCertificate = ConfigurationManager.AppSettings["ClientCertificate"];
        string ClientCertificatePassword = ConfigurationManager.AppSettings["ClientCertificatePassword"];
        string ServiceCertificate = ConfigurationManager.AppSettings["ServiceCertificate"];
        string ServiceCertificatePassword = ConfigurationManager.AppSettings["ServiceCertificatePassword"];

        var stsUrl = serverUrl + "Security.Sts/Security.Sts.svc";

        GenericXmlSecurityToken token = null;

        try
        {
            var customBinding = new CustomBinding();

            var securityBindingElement =
                (SymmetricSecurityBindingElement) SecurityBindingElement.CreateMutualCertificateBindingElement();
            securityBindingElement.SetKeyDerivation(true);
            securityBindingElement.MessageSecurityVersion =
                MessageSecurityVersion
                    .WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
            securityBindingElement.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt;
            securityBindingElement.RequireSignatureConfirmation = false;

            var securityTokenParameters = new UserNameSecurityTokenParameters()
            {
                InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient,
                RequireDerivedKeys = false
            };
            securityBindingElement.EndpointSupportingTokenParameters.SignedEncrypted.Add(securityTokenParameters);
            customBinding.Elements.Add(securityBindingElement);

            if (serverUrl.StartsWith("http:"))
                customBinding.Elements.Add(new HttpTransportBindingElement()
                {
                    MaxReceivedMessageSize = int.MaxValue,
                    MaxBufferPoolSize = int.MaxValue,
                    MaxBufferSize = int.MaxValue
                });
            else if (serverUrl.StartsWith("https:"))
                customBinding.Elements.Add(new HttpsTransportBindingElement()
                {
                    MaxReceivedMessageSize = int.MaxValue,
                    MaxBufferPoolSize = int.MaxValue,
                    MaxBufferSize = int.MaxValue
                });

            var stsChannelFactory = new WSTrustChannelFactory(customBinding,
                new EndpointAddress(new Uri(stsUrl), new DnsEndpointIdentity(identityDnsName)));

            stsChannelFactory.Credentials.SupportInteractive = false;

            stsChannelFactory.Credentials.ClientCertificate.Certificate = new X509Certificate2(ClientCertificate,
                ClientCertificatePassword);
            stsChannelFactory.Credentials.ServiceCertificate.DefaultCertificate =
                new X509Certificate2(ServiceCertificate, ServiceCertificatePassword);
            stsChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;

            stsChannelFactory.Credentials.UserName.UserName = username;
            stsChannelFactory.Credentials.UserName.Password = password;

            foreach (OperationDescription operationDescription in stsChannelFactory.Endpoint.Contract.Operations)
            {
                var operationBehavior =
                    operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
                if (operationBehavior != null)
                    operationBehavior.MaxItemsInObjectGraph = int.MaxValue;
            }

            var stsChannel = stsChannelFactory.CreateChannel();

            RequestSecurityToken request = new RequestSecurityToken();
            request.KeyType = "http://schemas.microsoft.com/idfx/keytype/symmetric";
            request.RequestType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue";
            request.TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1";
            token = (GenericXmlSecurityToken) stsChannel.Issue(request);

            return token;
        }
        catch (Exception e)
        {
            Console.WriteLine("GetToken Exception :" + e);
        }
        return token; 
    }
}

internal class SamlClientCredentials : ClientCredentials
{
    public GenericXmlSecurityToken Token { get; private set; }

    public SamlClientCredentials(GenericXmlSecurityToken token, ClientCredentials clientCredentials)
      : base(clientCredentials)
    {
        Token = token;
    }

    protected override ClientCredentials CloneCore()
    {
        return new SamlClientCredentials(Token, this);
    }

    public override SecurityTokenManager CreateSecurityTokenManager()
    {
        return new SamlSecurityTokenManager(this);
    }
}

internal class SamlSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
    private SamlClientCredentials clientCredentials;

    public SamlSecurityTokenManager(SamlClientCredentials clientCredentials)
      : base(clientCredentials)
    {
        this.clientCredentials = clientCredentials;
    }

    public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
    {
        if (tokenRequirement.TokenType == SecurityTokenTypes.Saml || tokenRequirement.TokenType == "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1")
            return new SamlSecurityTokenProvider(this.clientCredentials.Token);
        return base.CreateSecurityTokenProvider(tokenRequirement);
    }
}

internal class SamlSecurityTokenProvider : SecurityTokenProvider
{
    private readonly GenericXmlSecurityToken token;

    public SamlSecurityTokenProvider(GenericXmlSecurityToken token)
    {
        this.token = token;
    }

    protected override SecurityToken GetTokenCore(TimeSpan timeout)
    {
        return token;
    }
}

Solution

  • As I commented on the question : After a lot of time spent searching for a solution, It appears that WS2007FederationHttpBinding with Security Mode 'Message' is not meant to be used in this context because of the server identity check. Still it is possible to use the same certificate on the SSL offloader and for the service identity.

    This is the solution I chose to keep the security model and solve my problem