//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Threading; using System.Threading.Tasks; using DotNetOpenAuth.Configuration; /// /// A message expiration enforcing binding element that supports messages /// implementing the interface. /// internal class StandardExpirationBindingElement : IChannelBindingElement { /// /// A reusable pre-completed task that may be returned multiple times to reduce GC pressure. /// private static readonly Task NullTask = Task.FromResult(null); /// /// A reusable pre-completed task that may be returned multiple times to reduce GC pressure. /// private static readonly Task CompletedExpirationTask = Task.FromResult(MessageProtections.Expiration); /// /// Initializes a new instance of the class. /// internal StandardExpirationBindingElement() { } #region IChannelBindingElement Properties /// /// Gets the protection offered by this binding element. /// /// MessageProtections IChannelBindingElement.Protection { get { return MessageProtections.Expiration; } } /// /// Gets or sets the channel that this binding element belongs to. /// public Channel Channel { get; set; } #endregion /// /// Gets the maximum age a message implementing the /// interface can be before /// being discarded as too old. /// protected internal static TimeSpan MaximumMessageAge { get { return Configuration.DotNetOpenAuthSection.Messaging.MaximumMessageLifetime; } } #region IChannelBindingElement Methods /// /// Sets the timestamp on an outgoing message. /// /// The outgoing message. /// The cancellation token. /// /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// /// /// Implementations that provide message protection must honor the /// properties where applicable. /// public Task ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; if (expiringMessage != null) { expiringMessage.UtcCreationDate = DateTime.UtcNow; return CompletedExpirationTask; } return NullTask; } /// /// Reads the timestamp on a message and throws an exception if the message is too old. /// /// The incoming message. /// The cancellation token. /// /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// /// Thrown if the given message has already expired. /// /// Thrown when the binding element rules indicate that this message is invalid and should /// NOT be processed. /// public Task ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage; if (expiringMessage != null) { // Yes the UtcCreationDate is supposed to always be in UTC already, // but just in case a given message failed to guarantee that, we do it here. DateTime creationDate = expiringMessage.UtcCreationDate.ToUniversalTimeSafe(); DateTime expirationDate = creationDate + MaximumMessageAge; if (expirationDate < DateTime.UtcNow) { throw new ExpiredMessageException(expirationDate, expiringMessage); } // Mitigate HMAC attacks (just guessing the signature until they get it) by // disallowing post-dated messages. ErrorUtilities.VerifyProtocol( creationDate <= DateTime.UtcNow + DotNetOpenAuthSection.Messaging.MaximumClockSkew, MessagingStrings.MessageTimestampInFuture, creationDate); return CompletedExpirationTask; } return NullTask; } #endregion } }