//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using DotNetOpenAuth.Logging; using Validation; /// /// A binding element that checks/verifies a nonce message part. /// internal class StandardReplayProtectionBindingElement : IChannelBindingElement { /// /// A reusable, precompleted task that can be returned many times to reduce GC pressure. /// private static readonly Task NullTask = Task.FromResult(null); /// /// A reusable, precompleted task that can be returned many times to reduce GC pressure. /// private static readonly Task CompletedReplayProtectionTask = Task.FromResult(MessageProtections.ReplayProtection); /// /// These are the characters that may be chosen from when forming a random nonce. /// private const string AllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; /// /// The persistent store for nonces received. /// private INonceStore nonceStore; /// /// The length of generated nonces. /// private int nonceLength = 8; /// /// Initializes a new instance of the class. /// /// The store where nonces will be persisted and checked. internal StandardReplayProtectionBindingElement(INonceStore nonceStore) : this(nonceStore, false) { } /// /// Initializes a new instance of the class. /// /// The store where nonces will be persisted and checked. /// A value indicating whether zero-length nonces will be allowed. internal StandardReplayProtectionBindingElement(INonceStore nonceStore, bool allowEmptyNonces) { Requires.NotNull(nonceStore, "nonceStore"); this.nonceStore = nonceStore; this.AllowZeroLengthNonce = allowEmptyNonces; } #region IChannelBindingElement Properties /// /// Gets the protection that this binding element provides messages. /// public MessageProtections Protection { get { return MessageProtections.ReplayProtection; } } /// /// Gets or sets the channel that this binding element belongs to. /// public Channel Channel { get; set; } #endregion /// /// Gets or sets the strength of the nonce, which is measured by the number of /// nonces that could theoretically be generated. /// /// /// The strength of the nonce is equal to the number of characters that might appear /// in the nonce to the power of the length of the nonce. /// internal double NonceStrength { get { return Math.Pow(AllowedCharacters.Length, this.nonceLength); } set { value = Math.Max(value, AllowedCharacters.Length); this.nonceLength = (int)Math.Log(value, AllowedCharacters.Length); Debug.Assert(this.nonceLength > 0, "Nonce length calculated to be below 1!"); } } /// /// Gets or sets a value indicating whether empty nonces are allowed. /// /// Default is false. internal bool AllowZeroLengthNonce { get; set; } #region IChannelBindingElement Methods /// /// Applies a nonce to the message. /// /// The message to apply replay protection to. /// 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. /// public Task ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; if (nonceMessage != null) { nonceMessage.Nonce = this.GenerateUniqueFragment(); return CompletedReplayProtectionTask; } return NullTask; } /// /// Verifies that the nonce in an incoming message has not been seen before. /// /// 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 when the nonce check revealed a replayed message. /// /// Implementations that provide message protection must honor the /// properties where applicable. /// public Task ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage; if (nonceMessage != null && nonceMessage.Nonce != null) { ErrorUtilities.VerifyProtocol(nonceMessage.Nonce.Length > 0 || this.AllowZeroLengthNonce, MessagingStrings.InvalidNonceReceived); if (!this.nonceStore.StoreNonce(nonceMessage.NonceContext, nonceMessage.Nonce, nonceMessage.UtcCreationDate)) { Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", nonceMessage.Nonce, nonceMessage.UtcCreationDate); throw new ReplayedMessageException(message); } return CompletedReplayProtectionTask; } return NullTask; } #endregion /// /// Generates a string of random characters for use as a nonce. /// /// The nonce string. private string GenerateUniqueFragment() { return MessagingUtilities.GetRandomString(this.nonceLength, AllowedCharacters); } } }