File: System\ServiceModel\Security\SecurityProtocolFactory.cs
Web Access
Project: src\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IdentityModel.Selectors;
using System.Runtime;
using System.Security.Authentication.ExtendedProtection;
using System.ServiceModel.Channels;
using System.ServiceModel.Security.Tokens;
using System.Globalization;
using System.Threading.Tasks;
 
namespace System.ServiceModel.Security
{
    /*
     * Concrete implementations are required to be thread safe after
     * Open() is called;
 
     * instances of concrete protocol factories are scoped to a
     * channel/listener factory;
 
     * Each channel/listener factory must have a
     * SecurityProtocolFactory set on it before open/first use; the
     * factory instance cannot be changed once the factory is opened
     * or listening;
 
     * security protocol instances are scoped to a channel and will be
     * created by the Create calls on protocol factories;
 
     * security protocol instances are required to be thread-safe.
 
     * for typical subclasses, factory wide state and immutable
     * settings are expected to be on the ProtocolFactory itself while
     * channel-wide state is maintained internally in each security
     * protocol instance;
 
     * the security protocol instance set on a channel cannot be
     * changed; however, the protocol instance may change internal
     * state; this covers RM's SCT renego case; by keeping state
     * change internal to protocol instances, we get better
     * coordination with concurrent message security on channels;
 
     * the primary pivot in creating a security protocol instance is
     * initiator (client) vs. responder (server), NOT sender vs
     * receiver
 
     * Create calls for input and reply channels will contain the
     * listener-wide state (if any) created by the corresponding call
     * on the factory;
 
     */
    internal abstract class SecurityProtocolFactory : ISecurityCommunicationObject
    {
        internal const bool defaultAddTimestamp = true;
        internal const bool defaultDeriveKeys = true;
        internal const bool defaultDetectReplays = true;
        internal const string defaultMaxClockSkewString = "00:05:00";
        internal const string defaultReplayWindowString = "00:05:00";
        internal static readonly TimeSpan defaultMaxClockSkew = TimeSpan.Parse(defaultMaxClockSkewString, CultureInfo.InvariantCulture);
        internal static readonly TimeSpan defaultReplayWindow = TimeSpan.Parse(defaultReplayWindowString, CultureInfo.InvariantCulture);
        internal const int defaultMaxCachedNonces = 900000;
        internal const string defaultTimestampValidityDurationString = "00:05:00";
        internal static readonly TimeSpan defaultTimestampValidityDuration = TimeSpan.Parse(defaultTimestampValidityDurationString, CultureInfo.InvariantCulture);
        internal const SecurityHeaderLayout defaultSecurityHeaderLayout = SecurityHeaderLayout.Strict;
 
        private static ReadOnlyCollection<SupportingTokenAuthenticatorSpecification> s_emptyTokenAuthenticators;
        private bool _addTimestamp = defaultAddTimestamp;
        private bool _detectReplays = defaultDetectReplays;
        private bool _expectIncomingMessages;
        private bool _expectOutgoingMessages;
        private SecurityAlgorithmSuite _incomingAlgorithmSuite = SecurityAlgorithmSuite.Default;
 
        // per receiver protocol factory lists
        private ICollection<SupportingTokenAuthenticatorSpecification> _channelSupportingTokenAuthenticatorSpecification;
 
        private int _maxCachedNonces = defaultMaxCachedNonces;
        private TimeSpan _maxClockSkew = defaultMaxClockSkew;
        private NonceCache _nonceCache = null;
        private SecurityAlgorithmSuite _outgoingAlgorithmSuite = SecurityAlgorithmSuite.Default;
        private TimeSpan _replayWindow = defaultReplayWindow;
        private SecurityStandardsManager _standardsManager = SecurityStandardsManager.DefaultInstance;
        private SecurityTokenManager _securityTokenManager;
        private SecurityBindingElement _securityBindingElement;
        private string _requestReplyErrorPropertyName;
        private TimeSpan _timestampValidityDuration = defaultTimestampValidityDuration;
        private SecurityHeaderLayout _securityHeaderLayout;
#pragma warning disable CS0649 // Field is never assign to
        private bool _expectChannelBasicTokens;
        private bool _expectChannelSignedTokens;
        private bool _expectChannelEndorsingTokens;
#pragma warning restore CS0649 // Field is never assign to
        private BufferManager _streamBufferManager = null;
 
        protected SecurityProtocolFactory()
        {
            _channelSupportingTokenAuthenticatorSpecification = new Collection<SupportingTokenAuthenticatorSpecification>();
            //scopedSupportingTokenAuthenticatorSpecification = new Dictionary<string, ICollection<SupportingTokenAuthenticatorSpecification>>();
            CommunicationObject = new WrapperSecurityCommunicationObject(this);
        }
 
        internal SecurityProtocolFactory(SecurityProtocolFactory factory) : this()
        {
            if (factory == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(factory));
            }
 
            ActAsInitiator = factory.ActAsInitiator;
            _addTimestamp = factory._addTimestamp;
            _detectReplays = factory._detectReplays;
            _incomingAlgorithmSuite = factory._incomingAlgorithmSuite;
            _maxCachedNonces = factory._maxCachedNonces;
            _maxClockSkew = factory._maxClockSkew;
            _outgoingAlgorithmSuite = factory._outgoingAlgorithmSuite;
            _replayWindow = factory._replayWindow;
            _channelSupportingTokenAuthenticatorSpecification = new Collection<SupportingTokenAuthenticatorSpecification>(new List<SupportingTokenAuthenticatorSpecification>(factory._channelSupportingTokenAuthenticatorSpecification));
            _standardsManager = factory._standardsManager;
            _timestampValidityDuration = factory._timestampValidityDuration;
            _securityBindingElement = (SecurityBindingElement)factory._securityBindingElement?.Clone();
            _securityTokenManager = factory._securityTokenManager;
            _nonceCache = factory._nonceCache;
        }
 
        protected WrapperSecurityCommunicationObject CommunicationObject { get; }
 
        // The ActAsInitiator value is set automatically on Open and
        // remains unchanged thereafter.  ActAsInitiator is true for
        // the initiator of the message exchange, such as the sender
        // of a datagram, sender of a request and sender of either leg
        // of a duplex exchange.
        public bool ActAsInitiator { get; private set; }
 
        public BufferManager StreamBufferManager
        {
            get
            {
                if (_streamBufferManager == null)
                {
                    _streamBufferManager = BufferManager.CreateBufferManager(0, int.MaxValue);
                }
 
                return _streamBufferManager;
            }
            set
            {
                _streamBufferManager = value;
            }
        }
 
        public ExtendedProtectionPolicy ExtendedProtectionPolicy { get; set; }
 
        public bool AddTimestamp
        {
            get
            {
                return _addTimestamp;
            }
            set
            {
                ThrowIfImmutable();
                _addTimestamp = value;
            }
        }
 
        public bool DetectReplays
        {
            get
            {
                return _detectReplays;
            }
            set
            {
                ThrowIfImmutable();
                _detectReplays = value;
            }
        }
 
        private static ReadOnlyCollection<SupportingTokenAuthenticatorSpecification> EmptyTokenAuthenticators
        {
            get
            {
                if (s_emptyTokenAuthenticators == null)
                {
                    s_emptyTokenAuthenticators = Array.AsReadOnly(new SupportingTokenAuthenticatorSpecification[0]);
                }
                return s_emptyTokenAuthenticators;
            }
        }
 
        internal bool ExpectKeyDerivation { get; set; }
 
        internal bool ExpectSupportingTokens { get; set; }
 
        public SecurityAlgorithmSuite IncomingAlgorithmSuite
        {
            get
            {
                return _incomingAlgorithmSuite;
            }
            set
            {
                ThrowIfImmutable();
                _incomingAlgorithmSuite = value ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException(nameof(value)));
            }
        }
 
        public int MaxCachedNonces
        {
            get
            {
                return _maxCachedNonces;
            }
            set
            {
                ThrowIfImmutable();
                if (value <= 0)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(value)));
                }
                _maxCachedNonces = value;
            }
        }
 
        public TimeSpan MaxClockSkew
        {
            get
            {
                return _maxClockSkew;
            }
            set
            {
                ThrowIfImmutable();
                if (value < TimeSpan.Zero)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(value)));
                }
                _maxClockSkew = value;
            }
        }
 
        public NonceCache NonceCache
        {
            get
            {
                return _nonceCache;
            }
            set
            {
                ThrowIfImmutable();
                _nonceCache = value;
            }
        }
 
        public SecurityAlgorithmSuite OutgoingAlgorithmSuite
        {
            get
            {
                return _outgoingAlgorithmSuite;
            }
            set
            {
                ThrowIfImmutable();
                _outgoingAlgorithmSuite = value ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException(nameof(value)));
            }
        }
 
        public TimeSpan ReplayWindow
        {
            get
            {
                return _replayWindow;
            }
            set
            {
                ThrowIfImmutable();
                if (value <= TimeSpan.Zero)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(value), SRP.TimeSpanMustbeGreaterThanTimeSpanZero));
                }
                _replayWindow = value;
            }
        }
 
        public SecurityBindingElement SecurityBindingElement
        {
            get { return _securityBindingElement; }
            set
            {
                ThrowIfImmutable();
                if (value != null)
                {
                    value = (SecurityBindingElement)value.Clone();
                }
                _securityBindingElement = value;
            }
        }
 
        public SecurityTokenManager SecurityTokenManager
        {
            get { return _securityTokenManager; }
            set
            {
                ThrowIfImmutable();
                _securityTokenManager = value;
            }
        }
 
        public virtual bool SupportsDuplex
        {
            get
            {
                return false;
            }
        }
 
        public SecurityHeaderLayout SecurityHeaderLayout
        {
            get
            {
                return _securityHeaderLayout;
            }
            set
            {
                ThrowIfImmutable();
                _securityHeaderLayout = value;
            }
        }
 
        public virtual bool SupportsReplayDetection
        {
            get
            {
                return true;
            }
        }
 
        public virtual bool SupportsRequestReply
        {
            get
            {
                return true;
            }
        }
 
        public SecurityStandardsManager StandardsManager
        {
            get
            {
                return _standardsManager;
            }
            set
            {
                ThrowIfImmutable();
                _standardsManager = value ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException(nameof(value)));
            }
        }
 
        public TimeSpan TimestampValidityDuration
        {
            get
            {
                return _timestampValidityDuration;
            }
            set
            {
                ThrowIfImmutable();
                if (value <= TimeSpan.Zero)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(value), SRP.TimeSpanMustbeGreaterThanTimeSpanZero));
                }
                _timestampValidityDuration = value;
            }
        }
 
        internal MessageSecurityVersion MessageSecurityVersion { get; private set; }
 
        // ISecurityCommunicationObject members
        public TimeSpan DefaultOpenTimeout => ServiceDefaults.OpenTimeout;
 
        public TimeSpan DefaultCloseTimeout => ServiceDefaults.CloseTimeout;
 
        public void OnClosed()
        {
        }
 
        public void OnClosing()
        {
        }
 
        public void OnFaulted()
        {
        }
 
        public void OnOpened()
        {
        }
 
        public void OnOpening()
        {
        }
 
        public virtual void OnAbort()
        {
            if (!ActAsInitiator)
            {
                throw ExceptionHelper.PlatformNotSupported();
            }
        }
 
        public virtual Task OnCloseAsync(TimeSpan timeout)
        {
            if (!ActAsInitiator)
            {
                throw ExceptionHelper.PlatformNotSupported();
            }
            return Task.CompletedTask;
        }
 
        public SecurityProtocol CreateSecurityProtocol(EndpointAddress target, Uri via, object listenerSecurityState, bool isReturnLegSecurityRequired, TimeSpan timeout)
        {
            ThrowIfNotOpen();
            SecurityProtocol securityProtocol = OnCreateSecurityProtocol(target, via, listenerSecurityState, timeout);
            if (securityProtocol == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SRP.ProtocolFactoryCouldNotCreateProtocol));
            }
            return securityProtocol;
        }
 
        public virtual T GetProperty<T>()
        {
            if (typeof(T) == typeof(Collection<ISecurityContextSecurityTokenCache>))
            {
                ThrowIfNotOpen();
                Collection<ISecurityContextSecurityTokenCache> result = new Collection<ISecurityContextSecurityTokenCache>();
                if (_channelSupportingTokenAuthenticatorSpecification != null)
                {
                    foreach (SupportingTokenAuthenticatorSpecification spec in _channelSupportingTokenAuthenticatorSpecification)
                    {
                        if (spec.TokenAuthenticator is ISecurityContextSecurityTokenCacheProvider)
                        {
                            result.Add(((ISecurityContextSecurityTokenCacheProvider)spec.TokenAuthenticator).TokenCache);
                        }
                    }
                }
                return (T)(object)(result);
            }
            else
            {
                return default(T);
            }
        }
 
        protected abstract SecurityProtocol OnCreateSecurityProtocol(EndpointAddress target, Uri via, object listenerSecurityState, TimeSpan timeout);
 
        internal IList<SupportingTokenAuthenticatorSpecification> GetSupportingTokenAuthenticators(string action, out bool expectSignedTokens, out bool expectBasicTokens, out bool expectEndorsingTokens)
        {
            expectSignedTokens = _expectChannelSignedTokens;
            expectBasicTokens = _expectChannelBasicTokens;
            expectEndorsingTokens = _expectChannelEndorsingTokens;
            // in case the channelSupportingTokenAuthenticators is empty return null so that its Count does not get accessed.
            return (Object.ReferenceEquals(_channelSupportingTokenAuthenticatorSpecification, EmptyTokenAuthenticators)) ? null : (IList<SupportingTokenAuthenticatorSpecification>)_channelSupportingTokenAuthenticatorSpecification;
        }
 
        public virtual Task OnOpenAsync(TimeSpan timeout)
        {
            if (SecurityBindingElement == null)
            {
                OnPropertySettingsError(nameof(SecurityBindingElement), true);
            }
 
            if (SecurityTokenManager == null)
            {
                OnPropertySettingsError(nameof(SecurityTokenManager), true);
            }
 
            MessageSecurityVersion = _standardsManager.MessageSecurityVersion;
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            _expectOutgoingMessages = ActAsInitiator || SupportsRequestReply;
            _expectIncomingMessages = !ActAsInitiator || SupportsRequestReply;
            if (!ActAsInitiator)
            {
                throw ExceptionHelper.PlatformNotSupported();
            }
 
            if (DetectReplays)
            {
                if (!SupportsReplayDetection)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(DetectReplays), SRP.Format(SRP.SecurityProtocolCannotDoReplayDetection, this));
                }
                if (MaxClockSkew == TimeSpan.MaxValue || ReplayWindow == TimeSpan.MaxValue)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRP.NoncesCachedInfinitely));
                }
 
                // If DetectReplays is true and nonceCache is null then use the default InMemoryNonceCache. 
                if (_nonceCache == null)
                {
                    // The nonce needs to be cached for replayWindow + 2*clockSkew to eliminate replays
                    _nonceCache = new InMemoryNonceCache(ReplayWindow + MaxClockSkew + MaxClockSkew, MaxCachedNonces);
                }
            }
 
            //derivedKeyTokenAuthenticator = new NonValidatingSecurityTokenAuthenticator<DerivedKeySecurityToken>();
            return Task.CompletedTask;
        }
 
        public Task OpenAsync(bool actAsInitiator, TimeSpan timeout)
        {
            ActAsInitiator = actAsInitiator;
            return ((IAsyncCommunicationObject)CommunicationObject).OpenAsync(timeout);
        }
 
        public Task CloseAsync(bool aborted, TimeSpan timeout)
        {
            if (aborted)
            {
                CommunicationObject.Abort();
                return Task.CompletedTask;
            }
            else
            {
                return ((IAsyncCommunicationObject)CommunicationObject).CloseAsync(timeout);
            }
        }
 
        internal void OnPropertySettingsError(string propertyName, bool requiredForForwardDirection)
        {
            if (requiredForForwardDirection)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(
                    SRP.Format(SRP.PropertySettingErrorOnProtocolFactory, propertyName, this),
                    propertyName));
            }
            else if (_requestReplyErrorPropertyName == null)
            {
                _requestReplyErrorPropertyName = propertyName;
            }
        }
 
        internal void ThrowIfImmutable()
        {
            CommunicationObject.ThrowIfDisposedOrImmutable();
        }
 
        private void ThrowIfNotOpen()
        {
            CommunicationObject.ThrowIfNotOpened();
        }
    }
}