File: System\ServiceModel\Security\SpnegoTokenProvider.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.ServiceModel.Security
{
 
    using System;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.IdentityModel.Claims;
    using System.IdentityModel.Policy;
    using System.Net;
    using System.Runtime;
    using System.Security.Principal;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Diagnostics;
    using System.Xml;
 
    using SafeFreeCredentials = System.IdentityModel.SafeFreeCredentials;
    using System.ServiceModel.Description;
 
    class SpnegoTokenProvider : SspiNegotiationTokenProvider
    {
        TokenImpersonationLevel allowedImpersonationLevel = TokenImpersonationLevel.Identification;
        ICredentials clientCredential;
        IdentityVerifier identityVerifier = IdentityVerifier.CreateDefault();
        bool allowNtlm = true;
        bool authenticateServer = true;
        SafeFreeCredentials credentialsHandle;
        bool ownCredentialsHandle = false;
        bool interactiveNegoExLogonEnabled = true;
 
        public SpnegoTokenProvider(SafeFreeCredentials credentialsHandle)
            : this(credentialsHandle, null)
        { }
 
        public SpnegoTokenProvider(SafeFreeCredentials credentialsHandle, SecurityBindingElement securityBindingElement)
            : base(securityBindingElement)
        {
            this.credentialsHandle = credentialsHandle;
        }
 
        // settings
        public IdentityVerifier IdentityVerifier
        {
            get
            {
                return this.identityVerifier;
            }
            set
            {
                this.CommunicationObject.ThrowIfDisposedOrImmutable();
                this.identityVerifier = value;
            }
        }
 
        public TokenImpersonationLevel AllowedImpersonationLevel
        {
            get
            {
                return this.allowedImpersonationLevel;
            }
            set
            {
                this.CommunicationObject.ThrowIfDisposedOrImmutable();
 
                TokenImpersonationLevelHelper.Validate(value);
                if (value == TokenImpersonationLevel.None)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value",
                        String.Format(CultureInfo.InvariantCulture, SR.GetString(SR.SpnegoImpersonationLevelCannotBeSetToNone))));
                }
                this.allowedImpersonationLevel = value;
            }
        }
 
        public ICredentials ClientCredential
        {
            get
            {
                return this.clientCredential;
            }
            set
            {
                this.CommunicationObject.ThrowIfDisposedOrImmutable();
                this.clientCredential = value;
            }
        }
 
        public bool AllowNtlm
        {
            get
            {
                return this.allowNtlm;
            }
            set
            {
                this.CommunicationObject.ThrowIfDisposedOrImmutable();
                this.allowNtlm = value;
            }
        }
 
        public bool AuthenticateServer
        {
            get
            {
                return this.authenticateServer;
            }
            set
            {
                this.CommunicationObject.ThrowIfDisposedOrImmutable();
                this.authenticateServer = value;
            }
        }
 
        public bool InteractiveNegoExLogonEnabled
        {
            get
            {
                return this.interactiveNegoExLogonEnabled;
            }
            set
            {
                this.interactiveNegoExLogonEnabled = value;
            }
        }
 
        // overrides
        public override XmlDictionaryString NegotiationValueType
        {
            get
            {
                return XD.TrustApr2004Dictionary.SpnegoValueTypeUri;
            }
        }
 
        public override void OnOpening()
        {
            bool osIsGreaterThanXP = SecurityUtils.IsOsGreaterThanXP();
 
            base.OnOpening();
            if (this.credentialsHandle == null)
            {
                string packageName;
                if (!this.allowNtlm && !osIsGreaterThanXP)
                {
                    packageName = "Kerberos";
                }
                else
                {
                    packageName = "Negotiate";
                }
 
                NetworkCredential credential = null;
                if (this.clientCredential != null)
                {
                    credential = this.clientCredential.GetCredential(this.TargetAddress.Uri, packageName);
                }
 
                // if OS is less than 2k3 !NTLM is not supported, Windows SE 142400
                if (!this.allowNtlm && osIsGreaterThanXP)
                {
                    this.credentialsHandle = SecurityUtils.GetCredentialsHandle(packageName, credential, false, "!NTLM");
                }
                else
                {
                    this.credentialsHandle = SecurityUtils.GetCredentialsHandle(packageName, credential, false);
                }
 
                this.ownCredentialsHandle = true;
            }
        }
 
        public override void OnClose(TimeSpan timeout)
        {
            base.OnClose(timeout);
            FreeCredentialsHandle();
        }
 
        public override void OnAbort()
        {
            base.OnAbort();
            FreeCredentialsHandle();
        }
 
        void FreeCredentialsHandle()
        {
            if (this.credentialsHandle != null)
            {
                if (this.ownCredentialsHandle)
                {
                    this.credentialsHandle.Close();
                }
                this.credentialsHandle = null;
            }
        }
 
        protected override bool CreateNegotiationStateCompletesSynchronously(EndpointAddress target, Uri via)
        {
            return true;
        }
 
        protected override IAsyncResult BeginCreateNegotiationState(EndpointAddress target, Uri via, TimeSpan timeout, AsyncCallback callback, object state)
        {
            SspiNegotiationTokenProviderState sspiState = this.CreateNegotiationState(target, via, timeout);
            return new CompletedAsyncResult<SspiNegotiationTokenProviderState>(sspiState, callback, state);
        }
 
        protected override SspiNegotiationTokenProviderState EndCreateNegotiationState(IAsyncResult result)
        {
            return CompletedAsyncResult<SspiNegotiationTokenProviderState>.End(result);
        }
 
        protected override SspiNegotiationTokenProviderState CreateNegotiationState(EndpointAddress target, Uri via, TimeSpan timeout)
        {
            EnsureEndpointAddressDoesNotRequireEncryption(target);
 
            EndpointIdentity identity = null;
            if (this.identityVerifier == null)
            {
                identity = target.Identity;
            }
            else
            {
                this.identityVerifier.TryGetIdentity(target, out identity);
            }
 
            string spn;
            if (this.AuthenticateServer || !this.AllowNtlm)
            {
                spn = SecurityUtils.GetSpnFromIdentity(identity, target);
            }
            else
            {
                // if an SPN or UPN identity is configured (for example, in mixed mode SSPI), then 
                // use that identity for Negotiate
                Claim identityClaim = identity.IdentityClaim;
                if (identityClaim != null && (identityClaim.ClaimType == ClaimTypes.Spn || identityClaim.ClaimType == ClaimTypes.Upn))
                {
                    spn = identityClaim.Resource.ToString();
                }
                else
                {
                    spn = "host/" + target.Uri.DnsSafeHost;
                }
            }
 
            string packageName;
            if (!this.allowNtlm && !SecurityUtils.IsOsGreaterThanXP())
            {
                packageName = "Kerberos";
            }
            else
            {
                packageName = "Negotiate";
            }
 
            WindowsSspiNegotiation sspiNegotiation = new WindowsSspiNegotiation(packageName, this.credentialsHandle,
                this.AllowedImpersonationLevel, spn, true, this.InteractiveNegoExLogonEnabled, this.allowNtlm);
            return new SspiNegotiationTokenProviderState(sspiNegotiation);
        }
 
        protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateSspiNegotiation(ISspiNegotiation sspiNegotiation)
        {
            WindowsSspiNegotiation windowsNegotiation = (WindowsSspiNegotiation)sspiNegotiation;
            if (windowsNegotiation.IsValidContext == false)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.InvalidSspiNegotiation)));
            }
            if (this.AuthenticateServer && windowsNegotiation.IsMutualAuthFlag == false)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.CannotAuthenticateServer)));
            }
            SecurityTraceRecordHelper.TraceClientSpnego(windowsNegotiation);
 
            return SecurityUtils.CreatePrincipalNameAuthorizationPolicies(windowsNegotiation.ServicePrincipalName);
        }
    }
}