summaryrefslogtreecommitdiffstats
path: root/src/DotNetOAuth/Messaging
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOAuth/Messaging')
-rw-r--r--src/DotNetOAuth/Messaging/Bindings/INonceStore.cs37
-rw-r--r--src/DotNetOAuth/Messaging/Bindings/IReplayProtectedProtocolMessage.cs2
-rw-r--r--src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs48
-rw-r--r--src/DotNetOAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs135
-rw-r--r--src/DotNetOAuth/Messaging/Channel.cs7
-rw-r--r--src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs18
-rw-r--r--src/DotNetOAuth/Messaging/MessagingStrings.resx6
7 files changed, 252 insertions, 1 deletions
diff --git a/src/DotNetOAuth/Messaging/Bindings/INonceStore.cs b/src/DotNetOAuth/Messaging/Bindings/INonceStore.cs
new file mode 100644
index 0000000..0ff7e60
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/Bindings/INonceStore.cs
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------
+// <copyright file="INonceStore.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Bindings {
+ using System;
+
+ /// <summary>
+ /// Describes the contract a nonce store must fulfill.
+ /// </summary>
+ internal interface INonceStore {
+ /// <summary>
+ /// Stores a given nonce and timestamp.
+ /// </summary>
+ /// <param name="nonce">
+ /// A series of random characters.
+ /// </param>
+ /// <param name="timestamp">
+ /// The timestamp that together with the nonce string make it unique.
+ /// The timestamp may also be used by the data store to clear out old nonces.
+ /// </param>
+ /// <returns>
+ /// True if the nonce+timestamp (combination) was not previously in the database.
+ /// False if the nonce was stored previously with the same timestamp.
+ /// </returns>
+ /// <remarks>
+ /// The nonce must be stored for no less than the maximum time window a message may
+ /// be processed within before being discarded as an expired message.
+ /// If the binding element is applicable to your channel, this expiration window
+ /// is retrieved or set using the
+ /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property.
+ /// </remarks>
+ bool StoreNonce(string nonce, DateTime timestamp);
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Bindings/IReplayProtectedProtocolMessage.cs b/src/DotNetOAuth/Messaging/Bindings/IReplayProtectedProtocolMessage.cs
index e1a5674..bc685ce 100644
--- a/src/DotNetOAuth/Messaging/Bindings/IReplayProtectedProtocolMessage.cs
+++ b/src/DotNetOAuth/Messaging/Bindings/IReplayProtectedProtocolMessage.cs
@@ -15,7 +15,7 @@ namespace DotNetOAuth.Messaging.Bindings {
/// All replay-protected messages must also be set to expire so the nonces do not have
/// to be stored indefinitely.
/// </remarks>
- internal interface IReplayProtectedProtocolMessage : IProtocolMessage {
+ internal interface IReplayProtectedProtocolMessage : IExpiringProtocolMessage {
/// <summary>
/// Gets or sets the nonce that will protect the message from replay attacks.
/// </summary>
diff --git a/src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs b/src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs
new file mode 100644
index 0000000..29e1547
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------
+// <copyright file="NonceMemoryStore.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Bindings {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// An in-memory nonce store. Useful for single-server web applications.
+ /// NOT for web farms.
+ /// </summary>
+ internal class NonceMemoryStore : INonceStore {
+ #region INonceStore Members
+
+ /// <summary>
+ /// Stores a given nonce and timestamp.
+ /// </summary>
+ /// <param name="nonce">
+ /// A series of random characters.
+ /// </param>
+ /// <param name="timestamp">
+ /// The timestamp that together with the nonce string make it unique.
+ /// The timestamp may also be used by the data store to clear out old nonces.
+ /// </param>
+ /// <returns>
+ /// True if the nonce+timestamp (combination) was not previously in the database.
+ /// False if the nonce was stored previously with the same timestamp.
+ /// </returns>
+ /// <remarks>
+ /// The nonce must be stored for no less than the maximum time window a message may
+ /// be processed within before being discarded as an expired message.
+ /// If the binding element is applicable to your channel, this expiration window
+ /// is retrieved or set using the
+ /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property.
+ /// </remarks>
+ public bool StoreNonce(string nonce, DateTime timestamp) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs b/src/DotNetOAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs
new file mode 100644
index 0000000..5500a3b
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs
@@ -0,0 +1,135 @@
+//-----------------------------------------------------------------------
+// <copyright file="StandardReplayProtectionBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Bindings {
+ using System;
+ using System.Diagnostics;
+
+ /// <summary>
+ /// A binding element that checks/verifies a nonce message part.
+ /// </summary>
+ internal class StandardReplayProtectionBindingElement : IChannelBindingElement {
+ /// <summary>
+ /// These are the characters that may be chosen from when forming a random nonce.
+ /// </summary>
+ private const string AllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+ /// <summary>
+ /// The persistent store for nonces received.
+ /// </summary>
+ private INonceStore nonceStore;
+
+ /// <summary>
+ /// The length of generated nonces.
+ /// </summary>
+ private int nonceLength = 8;
+
+ /// <summary>
+ /// A random number generator.
+ /// </summary>
+ private Random generator = new Random();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StandardReplayProtectionBindingElement"/> class.
+ /// </summary>
+ /// <param name="nonceStore">The store where nonces will be persisted and checked.</param>
+ internal StandardReplayProtectionBindingElement(INonceStore nonceStore) {
+ if (nonceStore == null) {
+ throw new ArgumentNullException("nonceStore");
+ }
+
+ this.nonceStore = nonceStore;
+ }
+
+ #region IChannelBindingElement Properties
+
+ /// <summary>
+ /// Gets the protection that this binding element provides messages.
+ /// </summary>
+ public MessageProtection Protection {
+ get { return MessageProtection.ReplayProtection; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets or sets the strength of the nonce, which is measured by the number of
+ /// nonces that could theoretically be generated.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ 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!");
+ }
+ }
+
+ #region IChannelBindingElement Methods
+
+ /// <summary>
+ /// Applies a nonce to the message.
+ /// </summary>
+ /// <param name="message">The message to apply replay protection to.</param>
+ /// <returns>True if the message protection was applied. False otherwise.</returns>
+ public bool PrepareMessageForSending(IProtocolMessage message) {
+ IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage;
+ if (nonceMessage != null) {
+ nonceMessage.Nonce = this.GenerateUniqueFragment();
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Verifies that the nonce in an incoming message has not been seen before.
+ /// </summary>
+ /// <param name="message">The incoming message.</param>
+ /// <returns>
+ /// True if the message nonce passed replay detection checks.
+ /// False if the message did not have a nonce that could be checked at all.
+ /// </returns>
+ /// <exception cref="ReplayedMessageException">Thrown when the nonce check revealed a replayed message.</exception>
+ public bool PrepareMessageForReceiving(IProtocolMessage message) {
+ IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage;
+ if (nonceMessage != null) {
+ if (nonceMessage.Nonce == null || nonceMessage.Nonce.Length <= 0) {
+ throw new ProtocolException(MessagingStrings.InvalidNonceReceived);
+ }
+
+ if (!this.nonceStore.StoreNonce(nonceMessage.Nonce, nonceMessage.UtcCreationDate)) {
+ throw new ReplayedMessageException(message);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Generates a string of random characters for use as a nonce.
+ /// </summary>
+ /// <returns>The nonce string.</returns>
+ private string GenerateUniqueFragment() {
+ char[] nonce = new char[this.nonceLength];
+ for (int i = 0; i < nonce.Length; i++) {
+ nonce[i] = AllowedCharacters[this.generator.Next(AllowedCharacters.Length)];
+ }
+ return new string(nonce);
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Channel.cs b/src/DotNetOAuth/Messaging/Channel.cs
index 87c3bbe..d0ccccd 100644
--- a/src/DotNetOAuth/Messaging/Channel.cs
+++ b/src/DotNetOAuth/Messaging/Channel.cs
@@ -422,6 +422,13 @@ namespace DotNetOAuth.Messaging {
/// <returns>The properly ordered list of elements.</returns>
/// <exception cref="ProtocolException">Thrown when the binding elements are incomplete or inconsistent with each other.</exception>
private static IEnumerable<IChannelBindingElement> ValidateAndPrepareBindingElements(IEnumerable<IChannelBindingElement> elements) {
+ if (elements == null) {
+ return new IChannelBindingElement[0];
+ }
+ if (elements.Contains(null)) {
+ throw new ArgumentException(MessagingStrings.SequenceContainsNullElement, "elements");
+ }
+
// Filter the elements between the mere transforming ones and the protection ones.
var transformationElements = new List<IChannelBindingElement>(
elements.Where(element => element.Protection == MessageProtection.None));
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs
index 60e7c02..3444ea6 100644
--- a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs
+++ b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs
@@ -160,6 +160,15 @@ namespace DotNetOAuth.Messaging {
}
/// <summary>
+ /// Looks up a localized string similar to The incoming message had an invalid or missing nonce..
+ /// </summary>
+ internal static string InvalidNonceReceived {
+ get {
+ return ResourceManager.GetString("InvalidNonceReceived", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to An item with the same key has already been added..
/// </summary>
internal static string KeyAlreadyExists {
@@ -214,6 +223,15 @@ namespace DotNetOAuth.Messaging {
}
/// <summary>
+ /// Looks up a localized string similar to The list contains a null element..
+ /// </summary>
+ internal static string SequenceContainsNullElement {
+ get {
+ return ResourceManager.GetString("SequenceContainsNullElement", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Message signature was incorrect..
/// </summary>
internal static string SignatureInvalid {
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.resx b/src/DotNetOAuth/Messaging/MessagingStrings.resx
index b89ef4b..4f67059 100644
--- a/src/DotNetOAuth/Messaging/MessagingStrings.resx
+++ b/src/DotNetOAuth/Messaging/MessagingStrings.resx
@@ -150,6 +150,9 @@
<data name="InvalidMessageParts" xml:space="preserve">
<value>Some part(s) of the message have invalid values: {0}</value>
</data>
+ <data name="InvalidNonceReceived" xml:space="preserve">
+ <value>The incoming message had an invalid or missing nonce.</value>
+ </data>
<data name="KeyAlreadyExists" xml:space="preserve">
<value>An item with the same key has already been added.</value>
</data>
@@ -168,6 +171,9 @@
<data name="RequiredProtectionMissing" xml:space="preserve">
<value>The binding element offering the {0} protection requires other protection that is not provided.</value>
</data>
+ <data name="SequenceContainsNullElement" xml:space="preserve">
+ <value>The list contains a null element.</value>
+ </data>
<data name="SignatureInvalid" xml:space="preserve">
<value>Message signature was incorrect.</value>
</data>