//-----------------------------------------------------------------------
//
// 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);
}
}
}