//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Dispatcher
{
    using System.ServiceModel.Channels;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Security;
    using System.ServiceModel.Security.Tokens;
    using System.IdentityModel.Tokens;

    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Runtime.CompilerServices;
    using System.Net.Security;
    using System.Security.Principal;

    class SecurityValidationBehavior : IEndpointBehavior, IServiceBehavior
    {
        static SecurityValidationBehavior instance;

        public static SecurityValidationBehavior Instance
        {
            get
            {
                if (instance == null)
                    instance = new SecurityValidationBehavior();
                return instance;
            }
        }

        class ValidationBinding : Binding
        {
            Binding binding;
            BindingElementCollection elements;

            public ValidationBinding(Binding binding)
                : base(binding.Name, binding.Namespace)
            {
                this.binding = binding;
            }

            public override string Scheme
            {
                get { return this.binding.Scheme; }
            }

            public override BindingElementCollection CreateBindingElements()
            {
                if (this.elements == null)
                {
                    this.elements = this.binding.CreateBindingElements();
                }
                return this.elements;
            }

            public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingParameterCollection parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }
            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(params object[] parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, params object[] parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, params object[] parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, ListenUriMode listenUriMode, params object[] parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingParameterCollection parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, BindingParameterCollection parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, BindingParameterCollection parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(Uri listenUriBaseAddress, string listenUriRelativeAddress, ListenUriMode listenUriMode, BindingParameterCollection parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override bool CanBuildChannelFactory<TChannel>(BindingParameterCollection parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }

            public override bool CanBuildChannelListener<TChannel>(BindingParameterCollection parameters)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
            }
        }

        void IEndpointBehavior.Validate(ServiceEndpoint serviceEndpoint)
        {
            if (serviceEndpoint == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serviceEndpoint");
            SecurityBindingElement sbe;
            Binding binding = new ValidationBinding(serviceEndpoint.Binding);
            ValidateBinding(binding, serviceEndpoint.Contract, out sbe);
        }

        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection parameters)
        {
        }

        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime behavior)
        {
        }

        void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }

        void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            if (description == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("description");

            for (int i = 0; i < description.Endpoints.Count; i++)
            {
                ServiceEndpoint endpoint = description.Endpoints[i];
                Binding binding = new ValidationBinding(endpoint.Binding);
                SecurityBindingElement sbe;
                ValidateBinding(binding, endpoint.Contract, out sbe);
                if (sbe != null)
                {
                    SecurityTokenParameterInclusionModeRule.Validate(sbe, binding, endpoint.Contract, description.Behaviors);
                }
            }

            WindowsIdentitySupportRule.Validate(description);
            UsernameImpersonationRule.Validate(description);
            MissingClientCertificateRule.Validate(description);
        }

        void ValidateBinding(Binding binding, ContractDescription contract, out SecurityBindingElement securityBindingElement)
        {
            securityBindingElement = SecurityValidationBehavior.GetSecurityBinding(binding, contract);
            if (securityBindingElement != null)
                ValidateSecurityBinding(securityBindingElement, binding, contract);
            else
                ValidateNoSecurityBinding(binding, contract);

        }

        void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
        {
            ContractProtectionRequirementsRule.ValidateSecurityBinding(sbe, binding, contract);
            CookieAndSessionProtectionRequirementsRule.ValidateSecurityBinding(sbe, binding, contract);
            SoapOverSecureTransportRequirementsRule.ValidateSecurityBinding(sbe, binding, contract);
            SecurityVersionSupportForEncryptedKeyBindingRule.ValidateSecurityBinding(sbe, binding, contract);
            SecurityVersionSupportForThumbprintKeyIdentifierClauseRule.ValidateSecurityBinding(sbe, binding, contract);
            SecurityBindingSupportForOneWayOnlyRule.ValidateSecurityBinding(sbe, binding, contract);
            IssuedKeySizeCompatibilityWithAlgorithmSuiteRule.ValidateSecurityBinding(sbe, binding, contract);
            MessageSecurityAndManualAddressingRule.ValidateSecurityBinding(sbe, binding, contract);
            NoStreamingWithSecurityRule.ValidateSecurityBinding(sbe, binding, contract);
            UnknownHeaderProtectionRequirementsRule.ValidateSecurityBinding(sbe, binding, contract);
            BearerKeyTypeIssuanceRequirementRule.ValidateSecurityBinding(sbe, binding, contract);
        }

        void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
        {
            ContractProtectionRequirementsRule.ValidateNoSecurityBinding(binding, contract);
            CookieAndSessionProtectionRequirementsRule.ValidateNoSecurityBinding(binding, contract);
            SoapOverSecureTransportRequirementsRule.ValidateNoSecurityBinding(binding, contract);
            SecurityVersionSupportForEncryptedKeyBindingRule.ValidateNoSecurityBinding(binding, contract);
            SecurityVersionSupportForThumbprintKeyIdentifierClauseRule.ValidateNoSecurityBinding(binding, contract);
            SecurityBindingSupportForOneWayOnlyRule.ValidateNoSecurityBinding(binding, contract);
            IssuedKeySizeCompatibilityWithAlgorithmSuiteRule.ValidateNoSecurityBinding(binding, contract);
            MessageSecurityAndManualAddressingRule.ValidateNoSecurityBinding(binding, contract);
            UnknownHeaderProtectionRequirementsRule.ValidateNoSecurityBinding(binding, contract);
            BearerKeyTypeIssuanceRequirementRule.ValidateNoSecurityBinding(binding, contract);
        }

        static SecurityBindingElement GetSecurityBinding(Binding binding, ContractDescription contract)
        {
            SecurityBindingElement sbe = null;
            BindingElementCollection elements = binding.CreateBindingElements();
            for (int i = 0; i < elements.Count; i++)
            {
                BindingElement element = elements[i];
                if (element is SecurityBindingElement)
                {
                    if (sbe != null)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.MoreThanOneSecurityBindingElementInTheBinding, binding.Name, binding.Namespace, contract.Name, contract.Namespace)));
                    sbe = (SecurityBindingElement)element;
                }
            }
            return sbe;
        }


        internal void AfterBuildTimeValidation(ServiceDescription description)
        {
            S4UImpersonationRule.Validate(description);
        }

        // We do not allow streaming with message security which makes our service vulnerable
        // for example, GetWhitespace may be a problem if it’s called on unbounded data.
        static class NoStreamingWithSecurityRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                // check to see if we are doing message security
                // if transport security, the sbe would be transportsecuritybindingelement
                if (sbe is SymmetricSecurityBindingElement || sbe is AsymmetricSecurityBindingElement)
                {
                    // check to see if we are streaming
                    // ([....] 53690): need to have a general way get the transfer Mode from the binding
                    // TransferMode transferMode = binding.GetProperty<TransferMode>(new BindingParameterCollection());
                    if (GetTransferMode(binding) != TransferMode.Buffered)
                    {
                        // throw 
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.NoStreamingWithSecurity, binding.Name, binding.Namespace)));
                    }
                }
            }

            static TransferMode GetTransferMode(Binding binding)
            {
                TransferMode mode = TransferMode.Buffered;

                BindingElementCollection elements = binding.CreateBindingElements();
                TransportBindingElement element = elements.Find<TransportBindingElement>();

                if (element is ConnectionOrientedTransportBindingElement)
                {
                    mode = ((ConnectionOrientedTransportBindingElement)element).TransferMode;
                }
                else if (element is HttpTransportBindingElement)
                {
                    mode = ((HttpTransportBindingElement)element).TransferMode;
                }

                return mode;
            }
        }


        static class WindowsIdentitySupportRule
        {
            static public void Validate(ServiceDescription description)
            {
                bool impersonateCallerForAllServiceMethods = false;
                ServiceAuthorizationBehavior authorizationBehavior = description.Behaviors.Find<ServiceAuthorizationBehavior>();
                if (authorizationBehavior != null)
                {
                    impersonateCallerForAllServiceMethods = authorizationBehavior.ImpersonateCallerForAllOperations;
                }
                else
                {
                    impersonateCallerForAllServiceMethods = false;
                }
                for (int i = 0; i < description.Endpoints.Count; i++)
                {
                    ServiceEndpoint endpoint = description.Endpoints[i];
                    if (endpoint.InternalIsSystemEndpoint(description))
                    {
                        continue;
                    }
                    for (int j = 0; j < endpoint.Contract.Operations.Count; j++)
                    {
                        OperationDescription operation = endpoint.Contract.Operations[j];
                        OperationBehaviorAttribute operationBehavior = operation.Behaviors.Find<OperationBehaviorAttribute>();
                        if (impersonateCallerForAllServiceMethods &&
                            !operation.IsServerInitiated() &&
                            (operationBehavior == null || operationBehavior.Impersonation == ImpersonationOption.NotAllowed))
                        {
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.OperationDoesNotAllowImpersonation, operation.Name, endpoint.Contract.Name, endpoint.Contract.Namespace)));
                        }
                        if (impersonateCallerForAllServiceMethods || (operationBehavior != null && operationBehavior.Impersonation == ImpersonationOption.Required))
                        {
                            ValidateWindowsIdentityCapability(endpoint.Binding, endpoint.Contract, operation);
                        }
                    }
                }
            }

            static void ValidateWindowsIdentityCapability(Binding binding, ContractDescription contract, OperationDescription operation)
            {
                bool windowsIdentityProvided = false;

                ISecurityCapabilities capabilities = binding.GetProperty<ISecurityCapabilities>(new BindingParameterCollection());
                if (capabilities != null && capabilities.SupportsClientWindowsIdentity)
                {
                    windowsIdentityProvided = true;
                }

                if (!windowsIdentityProvided)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(SR.BindingDoesNotSupportWindowsIdenityForImpersonation, operation.Name, binding.Name, binding.Namespace, contract.Name, contract.Namespace)));
                }
            }
        }

        static class S4UImpersonationRule
        {
            const int WindowsServerMajorNumber = 5;
            const int WindowsServerMinorNumber = 2;

            static bool IsS4URequiredForImpersonation(SecurityBindingElement sbe)
            {
                foreach (SecurityTokenParameters stp in new SecurityTokenParametersEnumerable(sbe, true))
                {
                    if (stp is SecureConversationSecurityTokenParameters)
                    {
                        SecureConversationSecurityTokenParameters scstp = (SecureConversationSecurityTokenParameters)stp;

                        if (scstp.RequireCancellation == false)
                            return true;

                        if (scstp.BootstrapSecurityBindingElement != null)
                        {
                            return IsS4URequiredForImpersonation(scstp.BootstrapSecurityBindingElement);
                        }
                    }

                    if (stp is SspiSecurityTokenParameters
                        && ((SspiSecurityTokenParameters)stp).RequireCancellation == false)
                        return true;

                    if (stp is X509SecurityTokenParameters)
                        return true;
                }

                return false;
            }

            static public void Validate(ServiceDescription description)
            {
                ServiceAuthorizationBehavior behavior = description.Behaviors.Find<ServiceAuthorizationBehavior>();
                bool impersonateCallerForAllMethods = (behavior != null) ? behavior.ImpersonateCallerForAllOperations : false;
                for (int i = 0; i < description.Endpoints.Count; i++)
                {
                    ServiceEndpoint endpoint = description.Endpoints[i];
                    if (endpoint.InternalIsSystemEndpoint(description))
                    {
                        continue;
                    }

                    bool isImpersonationRequested = impersonateCallerForAllMethods;
                    if (!isImpersonationRequested)
                    {
                        isImpersonationRequested = ValidatorUtils.EndpointRequiresImpersonation(endpoint);
                    }
                    if (isImpersonationRequested)
                    {
                        ICollection<BindingElement> bindingElements = endpoint.Binding.CreateBindingElements();
                        foreach (BindingElement element in bindingElements)
                        {
                            SecurityBindingElement sbe = (element as SecurityBindingElement);
                            if (sbe != null)
                            {
                                if (IsS4URequiredForImpersonation(sbe))
                                {
                                    Version osVersion = Environment.OSVersion.Version;
                                    if ((osVersion.Major < WindowsServerMajorNumber)
                                        || ((osVersion.Major == WindowsServerMajorNumber) && (osVersion.Minor < WindowsServerMinorNumber)))
                                    {
                                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                                            SR.GetString(SR.CannotPerformS4UImpersonationOnPlatform, endpoint.Binding.Name, endpoint.Binding.Namespace, endpoint.Contract.Name, endpoint.Contract.Namespace)));
                                    }
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }

        static class UnknownHeaderProtectionRequirementsRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                if (sbe is SymmetricSecurityBindingElement || sbe is AsymmetricSecurityBindingElement)
                    ValidateContract(binding, contract, sbe.GetIndividualProperty<ISecurityCapabilities>().SupportedRequestProtectionLevel, sbe.GetIndividualProperty<ISecurityCapabilities>().SupportedResponseProtectionLevel);
                else
                    ValidateContract(binding, contract, ProtectionLevel.None, ProtectionLevel.None);
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
                ValidateContract(binding, contract, ProtectionLevel.None, ProtectionLevel.None);
            }

            static void ValidateContract(Binding binding, ContractDescription contract, ProtectionLevel defaultRequestProtectionLevel, ProtectionLevel defaultResponseProtectionLevel)
            {
                if (contract == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("contract"));

                ProtectionLevel contractScopeDefaultRequestProtectionLevel;
                ProtectionLevel contractScopeDefaultResponseProtectionLevel;
                if (contract.HasProtectionLevel)
                {
                    contractScopeDefaultRequestProtectionLevel = contract.ProtectionLevel;
                    contractScopeDefaultResponseProtectionLevel = contract.ProtectionLevel;
                }
                else
                {
                    contractScopeDefaultRequestProtectionLevel = defaultRequestProtectionLevel;
                    contractScopeDefaultResponseProtectionLevel = defaultResponseProtectionLevel;
                }

                foreach (OperationDescription operation in contract.Operations)
                {
                    ProtectionLevel operationScopeDefaultRequestProtectionLevel;
                    ProtectionLevel operationScopeDefaultResponseProtectionLevel;
                    if (operation.HasProtectionLevel)
                    {
                        operationScopeDefaultRequestProtectionLevel = operation.ProtectionLevel;
                        operationScopeDefaultResponseProtectionLevel = operation.ProtectionLevel;
                    }
                    else
                    {
                        operationScopeDefaultRequestProtectionLevel = contractScopeDefaultRequestProtectionLevel;
                        operationScopeDefaultResponseProtectionLevel = contractScopeDefaultResponseProtectionLevel;
                    }
                    foreach (MessageDescription message in operation.Messages)
                    {
                        ProtectionLevel messageScopeDefaultProtectionLevel;
                        if (message.HasProtectionLevel)
                        {
                            messageScopeDefaultProtectionLevel = message.ProtectionLevel;
                        }
                        else if (message.Direction == MessageDirection.Input)
                        {
                            messageScopeDefaultProtectionLevel = operationScopeDefaultRequestProtectionLevel;
                        }
                        else
                        {
                            messageScopeDefaultProtectionLevel = operationScopeDefaultResponseProtectionLevel;
                        }

                        foreach (MessageHeaderDescription header in message.Headers)
                        {
                            ProtectionLevel headerScopeDefaultProtectionLevel;

                            if (header.HasProtectionLevel)
                                headerScopeDefaultProtectionLevel = header.ProtectionLevel;
                            else
                                headerScopeDefaultProtectionLevel = messageScopeDefaultProtectionLevel;

                            //
                            // Finally we figured out the protection level for the individual header.
                            // We need to throw if the header is some unknown header, i.e., user can stick any Xml frag
                            // at the runtime, AND, its protection level is not ProtectionLevel.None
                            //
                            if (header.IsUnknownHeaderCollection && headerScopeDefaultProtectionLevel != ProtectionLevel.None)
                            {
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.UnknownHeaderCannotProtected, contract.Name, contract.Namespace, header.Name, header.Namespace)));
                            }
                        }
                    }
                }
            }
        }

        static class ContractProtectionRequirementsRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                if (sbe is SymmetricSecurityBindingElement || sbe is AsymmetricSecurityBindingElement)
                    ValidateContract(binding, contract, sbe.GetIndividualProperty<ISecurityCapabilities>().SupportedRequestProtectionLevel, sbe.GetIndividualProperty<ISecurityCapabilities>().SupportedResponseProtectionLevel);
                else
                    ValidateContract(binding, contract, ProtectionLevel.None, ProtectionLevel.None);
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
                ValidateContract(binding, contract, ProtectionLevel.None, ProtectionLevel.None);
            }

            static void ValidateContract(Binding binding, ContractDescription contract, ProtectionLevel defaultRequestProtectionLevel, ProtectionLevel defaultResponseProtectionLevel)
            {
                ProtectionLevel requestProtectionLevel;
                ProtectionLevel responseProtectionLevel;
                GetRequiredProtectionLevels(contract, defaultRequestProtectionLevel, defaultResponseProtectionLevel, out requestProtectionLevel, out responseProtectionLevel);
                ValidateBindingProtectionCapability(binding, contract, requestProtectionLevel, responseProtectionLevel);
            }

            static internal void GetRequiredProtectionLevels(ContractDescription contract, ProtectionLevel defaultRequestProtectionLevel, ProtectionLevel defaultResponseProtectionLevel, out ProtectionLevel request, out ProtectionLevel response)
            {
                ChannelProtectionRequirements requirements = ChannelProtectionRequirements.CreateFromContract(contract, defaultRequestProtectionLevel, defaultResponseProtectionLevel, false);

                if (requirements.IncomingSignatureParts.IsEmpty())
                {
                    request = ProtectionLevel.None;
                }
                else if (requirements.IncomingEncryptionParts.IsEmpty())
                {
                    request = ProtectionLevel.Sign;
                }
                else
                {
                    request = ProtectionLevel.EncryptAndSign;
                }

                if (requirements.OutgoingSignatureParts.IsEmpty())
                {
                    response = ProtectionLevel.None;
                }
                else if (requirements.OutgoingEncryptionParts.IsEmpty())
                {
                    response = ProtectionLevel.Sign;
                }
                else
                {
                    response = ProtectionLevel.EncryptAndSign;
                }
            }

            static void ValidateBindingProtectionCapability(Binding binding, ContractDescription contract, ProtectionLevel request, ProtectionLevel response)
            {
                bool requestValidated = request == ProtectionLevel.None;
                bool responseValidated = response == ProtectionLevel.None;

                if (!requestValidated || !responseValidated)
                {
                    ISecurityCapabilities capabilities = binding.GetProperty<ISecurityCapabilities>(new BindingParameterCollection());
                    if (capabilities != null)
                    {
                        if (!requestValidated)
                        {
                            requestValidated = ProtectionLevelHelper.IsStrongerOrEqual(capabilities.SupportedRequestProtectionLevel, request);
                        }
                        if (!responseValidated)
                        {
                            responseValidated = ProtectionLevelHelper.IsStrongerOrEqual(capabilities.SupportedResponseProtectionLevel, response);
                        }
                    }
                }

                if (!requestValidated)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(SR.AtLeastOneContractOperationRequestRequiresProtectionLevelNotSupportedByBinding, contract.Name, contract.Namespace, binding.Name, binding.Namespace)));
                }
                if (!responseValidated)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(SR.AtLeastOneContractOperationResponseRequiresProtectionLevelNotSupportedByBinding, contract.Name, contract.Namespace, binding.Name, binding.Namespace)));
                }
            }
        }

        static class BearerKeyTypeIssuanceRequirementRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                foreach (SecurityTokenParameters stp in new SecurityTokenParametersEnumerable(sbe, true))
                {
                    if (stp is IssuedSecurityTokenParameters)
                    {
                        IssuedSecurityTokenParameters issuedParameters = stp as IssuedSecurityTokenParameters;
                        if (issuedParameters.KeyType == System.IdentityModel.Tokens.SecurityKeyType.BearerKey)
                        {
                            // The issued Bearer token cannot be used as the primary protection token and it cannot be
                            // used as a Endorsing or Signed Endorsing token.
                            if ((sbe is SymmetricSecurityBindingElement) && IsBearerKeyType(((SymmetricSecurityBindingElement)sbe).ProtectionTokenParameters))
                            {
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.InvalidBearerKeyUsage, binding.Name, binding.Namespace)));
                            }

                            if ((sbe is AsymmetricSecurityBindingElement) && (IsBearerKeyType(((AsymmetricSecurityBindingElement)sbe).InitiatorTokenParameters) || IsBearerKeyType(((AsymmetricSecurityBindingElement)sbe).RecipientTokenParameters)))
                            {
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.InvalidBearerKeyUsage, binding.Name, binding.Namespace)));
                            }

                            foreach (SecurityTokenParameters tokenParam in sbe.EndpointSupportingTokenParameters.Endorsing)
                            {
                                if (IsBearerKeyType(tokenParam))
                                {
                                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.InvalidBearerKeyUsage, binding.Name, binding.Namespace)));
                                }
                            }

                            foreach (SecurityTokenParameters tokenParam in sbe.EndpointSupportingTokenParameters.SignedEndorsing)
                            {
                                if (IsBearerKeyType(tokenParam))
                                {
                                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.InvalidBearerKeyUsage, binding.Name, binding.Namespace)));
                                }
                            }
                        }

                        if (issuedParameters.IssuerBinding != null)
                        {
                            SecurityBindingElement secBindingEle = SecurityValidationBehavior.GetSecurityBinding(issuedParameters.IssuerBinding, contract);
                            if (secBindingEle != null)
                                ValidateSecurityBinding(secBindingEle, issuedParameters.IssuerBinding, contract);
                        }
                    }
                    else if (stp is SecureConversationSecurityTokenParameters)
                    {
                        SecureConversationSecurityTokenParameters scParameters = stp as SecureConversationSecurityTokenParameters;
                        ValidateSecurityBinding(scParameters.BootstrapSecurityBindingElement, binding, contract);
                    }
                }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }

            static bool IsBearerKeyType(SecurityTokenParameters tokenParameters)
            {
                if (!(tokenParameters is IssuedSecurityTokenParameters))
                    return false;

                return ((IssuedSecurityTokenParameters)tokenParameters).KeyType == SecurityKeyType.BearerKey;
            }

        }

        static class CookieAndSessionProtectionRequirementsRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                if (!(sbe is TransportSecurityBindingElement))
                    foreach (SecurityTokenParameters stp in new SecurityTokenParametersEnumerable(sbe, true))
                    {
                        SecureConversationSecurityTokenParameters scstp = stp as SecureConversationSecurityTokenParameters;
                        if (scstp != null)
                        {
                            ISecurityCapabilities bootstrapSecurityCapabilities = scstp.BootstrapSecurityBindingElement.GetIndividualProperty<ISecurityCapabilities>();
                            if (bootstrapSecurityCapabilities != null
                                && bootstrapSecurityCapabilities.SupportedRequestProtectionLevel == ProtectionLevel.EncryptAndSign
                                && bootstrapSecurityCapabilities.SupportedResponseProtectionLevel == ProtectionLevel.EncryptAndSign)
                            {
                                continue;
                            }

                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                                SR.GetString(SR.BindingDoesNotSupportProtectionForRst, binding.Name, binding.Namespace, contract.Name, contract.Namespace)));
                        }
                    }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }
        }

        static class SoapOverSecureTransportRequirementsRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement securityBindingElement, Binding binding, ContractDescription contract)
            {
                if (securityBindingElement is TransportSecurityBindingElement && !securityBindingElement.AllowInsecureTransport)
                {
                    // ensure that if soap security cookie/session is configured, then the authentication mode supports encryption
                    IEnumerable<BindingElement> elements = binding.CreateBindingElements();
                    Collection<BindingElement> bindingElementStack = new Collection<BindingElement>();
                    bool isBelowSecurity = false;
                    foreach (BindingElement element in elements)
                    {
                        SecurityBindingElement sbe = element as SecurityBindingElement;
                        if (sbe != null)
                        {
                            isBelowSecurity = true;
                        }
                        else if (isBelowSecurity)
                        {
                            bindingElementStack.Add(element);
                        }
                    }
                    bool isTransportProtected = false;
                    if (bindingElementStack.Count != 0)
                    {
                        BindingContext context = new BindingContext(new CustomBinding(bindingElementStack), new BindingParameterCollection());
                        ISecurityCapabilities transportCapabilities = context.GetInnerProperty<ISecurityCapabilities>();
                        if (transportCapabilities != null
                            && transportCapabilities.SupportsServerAuthentication
                            && transportCapabilities.SupportedRequestProtectionLevel == ProtectionLevel.EncryptAndSign
                            && transportCapabilities.SupportedResponseProtectionLevel == ProtectionLevel.EncryptAndSign)
                        {
                            isTransportProtected = true;
                        }
                    }

                    if (!isTransportProtected)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.TransportDoesNotProtectMessage, binding.Name, binding.Namespace, contract.Name, contract.Namespace)));
                    }
                }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }
        }

        static class IssuedKeySizeCompatibilityWithAlgorithmSuiteRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                SecurityAlgorithmSuite algorithmSuite = sbe.DefaultAlgorithmSuite;
                foreach (SecurityTokenParameters stp in new SecurityTokenParametersEnumerable(sbe, true))
                {
                    if (stp is IssuedSecurityTokenParameters)
                    {
                        IssuedSecurityTokenParameters issuedParameters = stp as IssuedSecurityTokenParameters;
                        if (issuedParameters.KeySize != 0)
                        {
                            bool isCompatible = true;
                            if (issuedParameters.KeyType == System.IdentityModel.Tokens.SecurityKeyType.SymmetricKey &&
                                !sbe.DefaultAlgorithmSuite.IsSymmetricKeyLengthSupported(issuedParameters.KeySize))
                            {
                                isCompatible = false;

                            }
                            else if (issuedParameters.KeyType == System.IdentityModel.Tokens.SecurityKeyType.AsymmetricKey &&
                                !sbe.DefaultAlgorithmSuite.IsAsymmetricKeyLengthSupported(issuedParameters.KeySize))
                            {
                                isCompatible = false;
                            }
                            if (!isCompatible)
                            {
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.IssuedKeySizeNotCompatibleWithAlgorithmSuite, binding.Name, binding.Namespace, sbe.DefaultAlgorithmSuite, issuedParameters.KeySize)));
                            }
                        }
                    }
                    else if (stp is SecureConversationSecurityTokenParameters)
                    {
                        SecureConversationSecurityTokenParameters scParameters = stp as SecureConversationSecurityTokenParameters;
                        ValidateSecurityBinding(scParameters.BootstrapSecurityBindingElement, binding, contract);
                    }
                }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }
        }

        static class SecurityTokenParameterInclusionModeRule
        {
            static void EnforceInclusionMode(Binding binding, SecurityTokenParameters stp, params SecurityTokenInclusionMode[] allowedInclusionModes)
            {
                bool isMatch = false;
                for (int i = 0; i < allowedInclusionModes.Length; ++i)
                {
                    if (stp.InclusionMode == allowedInclusionModes[i])
                    {
                        isMatch = true;
                        break;
                    }
                }
                if (!isMatch)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SecurityTokenParametersHasIncompatibleInclusionMode, binding.Name, binding.Namespace, stp.GetType(), stp.InclusionMode, allowedInclusionModes[0])));
                }
            }

            static public void Validate(SecurityBindingElement sbe, Binding binding, ContractDescription contract, KeyedByTypeCollection<IServiceBehavior> behaviors)
            {
                if (behaviors != null)
                {
                    ServiceCredentials serviceCredentials = behaviors.Find<ServiceCredentials>();
                    if (serviceCredentials != null && serviceCredentials.GetType() != typeof(ServiceCredentials))
                    {
                        // A custom service credentials has been plugged in. Dont validate the binding
                        return;
                    }
                }
                SymmetricSecurityBindingElement ssbe = (sbe as SymmetricSecurityBindingElement);
                AsymmetricSecurityBindingElement asbe = (sbe as AsymmetricSecurityBindingElement);
                foreach (SecurityTokenParameters stp in new SecurityTokenParametersEnumerable(sbe, true))
                {
                    if (stp is RsaSecurityTokenParameters)
                    {
                        // rsa keys can only be referred to using keyinfo. There's no wire format for 
                        // serializing them
                        EnforceInclusionMode(binding, stp, SecurityTokenInclusionMode.Never);
                        continue;
                    }
                    if (stp is SecureConversationSecurityTokenParameters)
                    {
                        Validate(((SecureConversationSecurityTokenParameters)stp).BootstrapSecurityBindingElement, binding, contract, behaviors);
                    }
                    if (ssbe != null)
                    {
                        // for the protection token, if it is asymmetric inclusion mode should be Never
                        // all other cases inclusion mode should be AlwaysToRecipient/Once
                        if (ssbe.ProtectionTokenParameters == stp && stp.HasAsymmetricKey)
                        {
                            EnforceInclusionMode(binding, stp, SecurityTokenInclusionMode.Never);
                        }
                        else
                        {
                            EnforceInclusionMode(binding, stp, SecurityTokenInclusionMode.AlwaysToRecipient, SecurityTokenInclusionMode.Once);
                        }
                    }
                    else if (asbe != null)
                    {
                        if (asbe.InitiatorTokenParameters == stp && stp.HasAsymmetricKey)
                        {
                            // allow AlwaysToRecipient, Once and AlwaysToInitiator in this case since the duplex binding
                            // configures AlwaysToInitiator in this case
                            EnforceInclusionMode(binding, stp, SecurityTokenInclusionMode.AlwaysToRecipient, SecurityTokenInclusionMode.AlwaysToInitiator, SecurityTokenInclusionMode.Once);
                        }
                        else
                        {
                            EnforceInclusionMode(binding, stp, SecurityTokenInclusionMode.AlwaysToRecipient, SecurityTokenInclusionMode.Once);
                        }

                    }
                    else
                    {
                        EnforceInclusionMode(binding, stp, SecurityTokenInclusionMode.AlwaysToRecipient, SecurityTokenInclusionMode.Once);
                    }
                }
            }
        }

        static class SecurityVersionSupportForEncryptedKeyBindingRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                SymmetricSecurityBindingElement ssbe = sbe as SymmetricSecurityBindingElement;
                if (sbe.MessageSecurityVersion.SecurityVersion == SecurityVersion.WSSecurity10
                    && ssbe != null
                    && ssbe.ProtectionTokenParameters != null
                    && ssbe.ProtectionTokenParameters.HasAsymmetricKey)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(SR.SecurityVersionDoesNotSupportEncryptedKeyBinding, binding.Name, binding.Namespace, contract.Name, contract.Namespace, SecurityVersion.WSSecurity11)));
                }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }
        }

        static class SecurityVersionSupportForThumbprintKeyIdentifierClauseRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                if (sbe.MessageSecurityVersion.SecurityVersion == SecurityVersion.WSSecurity10)
                {
                    foreach (SecurityTokenParameters stp in new SecurityTokenParametersEnumerable(sbe))
                    {
                        X509SecurityTokenParameters x509 = stp as X509SecurityTokenParameters;
                        if (x509 != null && x509.X509ReferenceStyle == X509KeyIdentifierClauseType.Thumbprint)
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                                SR.GetString(SR.SecurityVersionDoesNotSupportThumbprintX509KeyIdentifierClause, binding.Name, binding.Namespace, contract.Name, contract.Namespace, SecurityVersion.WSSecurity11)));
                    }
                }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }
        }

        static class MessageSecurityAndManualAddressingRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                TransportBindingElement transport = binding.CreateBindingElements().Find<TransportBindingElement>();
                if (transport != null && transport.ManualAddressing)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                                SR.GetString(SR.MessageSecurityDoesNotWorkWithManualAddressing, binding.Name, binding.Namespace)));
                }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }
        }

        static class SecurityBindingSupportForOneWayOnlyRule
        {
            static public void ValidateSecurityBinding(SecurityBindingElement sbe, Binding binding, ContractDescription contract)
            {
                if (sbe is AsymmetricSecurityBindingElement && ((AsymmetricSecurityBindingElement)sbe).IsCertificateSignatureBinding)
                {
                    for (int i = 0; i < contract.Operations.Count; i++)
                    {
                        OperationDescription operation = contract.Operations[i];
                        if (!operation.IsOneWay)
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                                SR.GetString(SR.SecurityBindingSupportsOneWayOnly, binding.Name, binding.Namespace, contract.Name, contract.Namespace)));
                    }
                }
            }

            static public void ValidateNoSecurityBinding(Binding binding, ContractDescription contract)
            {
            }
        }

        static class MissingClientCertificateRule
        {
            static void ValidateCore(ServiceDescription description, ServiceCredentials credentials)
            {
                for (int i = 0; i < description.Endpoints.Count; i++)
                {
                    ServiceEndpoint endpoint = description.Endpoints[i];
                    BindingElementCollection elements = endpoint.Binding.CreateBindingElements();

                    SecurityBindingElement security = elements.Find<SecurityBindingElement>();
                    CompositeDuplexBindingElement duplex = elements.Find<CompositeDuplexBindingElement>();
                    if (security != null && duplex != null && SecurityBindingElement.IsMutualCertificateDuplexBinding(security))
                    {
                        //
                        // We only throw when we have 
                        // 1. a MutualCertificateDuplexBindingElement,
                        // 2. missing client certificate on the service side
                        // 3. The server will encrypt the response, or the message going from server to client
                        //
                        if (credentials.ClientCertificate.Certificate == null)
                        {
                            ProtectionLevel requestProtectionLevel;
                            ProtectionLevel responseProtectionLevel;
                            ContractProtectionRequirementsRule.GetRequiredProtectionLevels(endpoint.Contract, security.GetIndividualProperty<ISecurityCapabilities>().SupportedRequestProtectionLevel, security.GetIndividualProperty<ISecurityCapabilities>().SupportedResponseProtectionLevel,
                                out requestProtectionLevel, out responseProtectionLevel);

                            if (responseProtectionLevel == ProtectionLevel.EncryptAndSign)
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.NoClientCertificate, endpoint.Binding.Name, endpoint.Binding.Namespace)));

                        }
                    }
                }
            }

            static public void Validate(ServiceDescription description)
            {
                //
                // Verify if the service credentials are not customized
                //
                if (!description.Behaviors.Contains(typeof(ServiceCredentials)))
                    return;

                ValidateCore(description, description.Behaviors.Find<ServiceCredentials>());
            }
        }

        static class UsernameImpersonationRule
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            static void ValidateCore(ServiceDescription description, ServiceCredentials credentials)
            {
                if (credentials.UserNameAuthentication.UserNamePasswordValidationMode == UserNamePasswordValidationMode.Windows)
                {
                    return;
                }

                ServiceAuthorizationBehavior behavior = description.Behaviors.Find<ServiceAuthorizationBehavior>();
                bool impersonateCallerForAllMethods = (behavior != null) ? behavior.ImpersonateCallerForAllOperations : false;
                for (int i = 0; i < description.Endpoints.Count; i++)
                {
                    ServiceEndpoint endpoint = description.Endpoints[i];
                    if (endpoint.InternalIsSystemEndpoint(description))
                    {
                        continue;
                    }

                    if (ValidatorUtils.IsStandardBinding(endpoint.Binding))
                    {
                        bool isImpersonationRequested = impersonateCallerForAllMethods;
                        if (!isImpersonationRequested)
                        {
                            isImpersonationRequested = ValidatorUtils.EndpointRequiresImpersonation(endpoint);
                        }
                        if (isImpersonationRequested)
                        {
                            ICollection<BindingElement> bindingElements = endpoint.Binding.CreateBindingElements();
                            foreach (BindingElement element in bindingElements)
                            {
                                SecurityBindingElement sbe = (element as SecurityBindingElement);
                                if (sbe != null)
                                {
                                    ValidateSecurityBindingElement(sbe, endpoint);
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            static public void Validate(ServiceDescription description)
            {
                ServiceCredentials credentials = description.Behaviors.Find<ServiceCredentials>();
                if (credentials == null)
                    return;
                ValidateCore(description, credentials);
            }

            static private void ValidateSecurityBindingElement(SecurityBindingElement sbe, ServiceEndpoint endpoint)
            {
                if (sbe == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("sbe");

                if (endpoint == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint");

                foreach (SecurityTokenParameters stp in new SecurityTokenParametersEnumerable(sbe, true))
                {
                    if (stp is UserNameSecurityTokenParameters)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.CannotPerformImpersonationOnUsernameToken, endpoint.Binding.Name, endpoint.Binding.Namespace, endpoint.Contract.Name, endpoint.Contract.Namespace)));
                    }
                    else if (stp is SecureConversationSecurityTokenParameters)
                    {
                        ValidateSecurityBindingElement(((SecureConversationSecurityTokenParameters)stp).BootstrapSecurityBindingElement, endpoint);
                    }
                }
            }

        }

        static class ValidatorUtils
        {
            static public bool EndpointRequiresImpersonation(ServiceEndpoint endpoint)
            {
                if (endpoint == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint");

                for (int i = 0; i < endpoint.Contract.Operations.Count; ++i)
                {
                    OperationDescription operation = endpoint.Contract.Operations[i];
                    OperationBehaviorAttribute operationBehavior = operation.Behaviors.Find<OperationBehaviorAttribute>();
                    if (operationBehavior != null && (operationBehavior.Impersonation == ImpersonationOption.Required))
                    {
                        return true;
                    }
                }

                return false;
            }

            static public bool IsStandardBinding(Binding binding)
            {
                return (binding is BasicHttpBinding) ||
                    (binding is BasicHttpsBinding) ||
                    (binding is NetTcpBinding) ||
                    (binding is NetMsmqBinding) ||
                    (binding is NetNamedPipeBinding) ||
#pragma warning disable 0618
                    (binding is NetPeerTcpBinding) ||
#pragma warning restore 0618	                    
                    (binding is WSDualHttpBinding) ||
                    (binding is WSFederationHttpBinding) ||
                    (binding is WSHttpBinding) ||
                    (binding is NetHttpBinding) ||
                    (binding is NetHttpsBinding);
            }
        }

    }
}
