summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOAuth.Test/ChannelElements/OAuthChannelTests.cs (renamed from src/DotNetOAuth.Test/OAuthChannelTests.cs)26
-rw-r--r--src/DotNetOAuth.Test/DotNetOAuth.Test.csproj2
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestWebRequestHandler.cs1
-rw-r--r--src/DotNetOAuth/ChannelElements/IOAuthProtocolDirectedMessage.cs (renamed from src/DotNetOAuth/Messages/IOAuthProtocolDirectedMessage.cs)2
-rw-r--r--src/DotNetOAuth/ChannelElements/ITamperResistantOAuthMessage.cs (renamed from src/DotNetOAuth/Messages/ITamperResistantOAuthMessage.cs)2
-rw-r--r--src/DotNetOAuth/ChannelElements/IWebRequestHandler.cs (renamed from src/DotNetOAuth/IWebRequestHandler.cs)2
-rw-r--r--src/DotNetOAuth/ChannelElements/OAuthChannel.cs (renamed from src/DotNetOAuth/OAuthChannel.cs)15
-rw-r--r--src/DotNetOAuth/ChannelElements/OAuthMessageTypeProvider.cs (renamed from src/DotNetOAuth/OAuthMessageTypeProvider.cs)2
-rw-r--r--src/DotNetOAuth/ChannelElements/RsaSha1SigningBindingElement.cs37
-rw-r--r--src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs78
-rw-r--r--src/DotNetOAuth/ChannelElements/StandardWebRequestHandler.cs (renamed from src/DotNetOAuth/StandardWebRequestHandler.cs)2
-rw-r--r--src/DotNetOAuth/DotNetOAuth.csproj17
-rw-r--r--src/DotNetOAuth/Messages/MessageBase.cs3
-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
20 files changed, 417 insertions, 25 deletions
diff --git a/src/DotNetOAuth.Test/OAuthChannelTests.cs b/src/DotNetOAuth.Test/ChannelElements/OAuthChannelTests.cs
index 7677986..9a98728 100644
--- a/src/DotNetOAuth.Test/OAuthChannelTests.cs
+++ b/src/DotNetOAuth.Test/ChannelElements/OAuthChannelTests.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOAuth.Test {
+namespace DotNetOAuth.Test.ChannelElements {
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -13,7 +13,9 @@ namespace DotNetOAuth.Test {
using System.Text;
using System.Web;
using System.Xml;
+ using DotNetOAuth.ChannelElements;
using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
using DotNetOAuth.Test.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -21,23 +23,37 @@ namespace DotNetOAuth.Test {
public class OAuthChannelTests : TestBase {
private OAuthChannel channel;
private TestWebRequestHandler webRequestHandler;
+ private SigningBindingElementBase signingElement;
+ private INonceStore nonceStore;
[TestInitialize]
public override void SetUp() {
base.SetUp();
this.webRequestHandler = new TestWebRequestHandler();
- this.channel = new OAuthChannel(new TestMessageTypeProvider(), this.webRequestHandler);
+ this.signingElement = new RsaSha1SigningBindingElement();
+ this.nonceStore = new NonceMemoryStore();
+ this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new TestMessageTypeProvider(), this.webRequestHandler);
}
[TestMethod, ExpectedException(typeof(ArgumentNullException))]
public void CtorNullHandler() {
- new OAuthChannel(new TestMessageTypeProvider(), null);
+ new OAuthChannel(this.signingElement, this.nonceStore, new TestMessageTypeProvider(), null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void CtorNullSigner() {
+ new OAuthChannel(null, this.nonceStore, new TestMessageTypeProvider(), this.webRequestHandler);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullStore() {
+ new OAuthChannel(this.signingElement, null, new TestMessageTypeProvider(), this.webRequestHandler);
}
[TestMethod]
- public void CtorDefault() {
- new OAuthChannel();
+ public void CtorSimple() {
+ new OAuthChannel(this.signingElement, this.nonceStore);
}
[TestMethod]
diff --git a/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj b/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj
index dfa7fef..63ad5a2 100644
--- a/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj
+++ b/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj
@@ -81,7 +81,7 @@
<Compile Include="Mocks\TestSignedDirectedMessage.cs" />
<Compile Include="Mocks\MockSigningBindingElement.cs" />
<Compile Include="Mocks\TestWebRequestHandler.cs" />
- <Compile Include="OAuthChannelTests.cs" />
+ <Compile Include="ChannelElements\OAuthChannelTests.cs" />
<Compile Include="Mocks\TestChannel.cs" />
<Compile Include="Mocks\TestMessage.cs" />
<Compile Include="Mocks\TestMessageTypeProvider.cs" />
diff --git a/src/DotNetOAuth.Test/Mocks/TestWebRequestHandler.cs b/src/DotNetOAuth.Test/Mocks/TestWebRequestHandler.cs
index 33a2966..751bf0e 100644
--- a/src/DotNetOAuth.Test/Mocks/TestWebRequestHandler.cs
+++ b/src/DotNetOAuth.Test/Mocks/TestWebRequestHandler.cs
@@ -9,6 +9,7 @@ namespace DotNetOAuth.Test.Mocks {
using System.IO;
using System.Net;
using System.Text;
+ using DotNetOAuth.ChannelElements;
using DotNetOAuth.Messaging;
internal class TestWebRequestHandler : IWebRequestHandler {
diff --git a/src/DotNetOAuth/Messages/IOAuthProtocolDirectedMessage.cs b/src/DotNetOAuth/ChannelElements/IOAuthProtocolDirectedMessage.cs
index dea8de8..6399c9c 100644
--- a/src/DotNetOAuth/Messages/IOAuthProtocolDirectedMessage.cs
+++ b/src/DotNetOAuth/ChannelElements/IOAuthProtocolDirectedMessage.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOAuth.Messages {
+namespace DotNetOAuth.ChannelElements {
using DotNetOAuth.Messaging;
/// <summary>
diff --git a/src/DotNetOAuth/Messages/ITamperResistantOAuthMessage.cs b/src/DotNetOAuth/ChannelElements/ITamperResistantOAuthMessage.cs
index 3d4f8c0..c00ff8b 100644
--- a/src/DotNetOAuth/Messages/ITamperResistantOAuthMessage.cs
+++ b/src/DotNetOAuth/ChannelElements/ITamperResistantOAuthMessage.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOAuth.Messages {
+namespace DotNetOAuth.ChannelElements {
using DotNetOAuth.Messaging.Bindings;
/// <summary>
diff --git a/src/DotNetOAuth/IWebRequestHandler.cs b/src/DotNetOAuth/ChannelElements/IWebRequestHandler.cs
index 6281d5c..13b57c7 100644
--- a/src/DotNetOAuth/IWebRequestHandler.cs
+++ b/src/DotNetOAuth/ChannelElements/IWebRequestHandler.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOAuth {
+namespace DotNetOAuth.ChannelElements {
using System.IO;
using System.Net;
using DotNetOAuth.Messaging;
diff --git a/src/DotNetOAuth/OAuthChannel.cs b/src/DotNetOAuth/ChannelElements/OAuthChannel.cs
index bb8005e..7a91a98 100644
--- a/src/DotNetOAuth/OAuthChannel.cs
+++ b/src/DotNetOAuth/ChannelElements/OAuthChannel.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOAuth {
+namespace DotNetOAuth.ChannelElements {
using System;
using System.Collections.Generic;
using System.IO;
@@ -12,6 +12,7 @@ namespace DotNetOAuth {
using System.Text;
using System.Web;
using DotNetOAuth.Messaging;
+using DotNetOAuth.Messaging.Bindings;
/// <summary>
/// An OAuth-specific implementation of the <see cref="Channel"/> class.
@@ -26,13 +27,17 @@ namespace DotNetOAuth {
/// <summary>
/// Initializes a new instance of the <see cref="OAuthChannel"/> class.
/// </summary>
- internal OAuthChannel()
- : this(new OAuthMessageTypeProvider(), new StandardWebRequestHandler()) {
+ /// <param name="signingBindingElement">The binding element to use for signing.</param>
+ /// <param name="store">The web application store to use for nonces.</param>
+ internal OAuthChannel(SigningBindingElementBase signingBindingElement, INonceStore store)
+ : this(signingBindingElement, store, new OAuthMessageTypeProvider(), new StandardWebRequestHandler()) {
}
/// <summary>
/// Initializes a new instance of the <see cref="OAuthChannel"/> class.
/// </summary>
+ /// <param name="signingBindingElement">The binding element to use for signing.</param>
+ /// <param name="store">The web application store to use for nonces.</param>
/// <param name="messageTypeProvider">
/// An injected message type provider instance.
/// Except for mock testing, this should always be <see cref="OAuthMessageTypeProvider"/>.
@@ -44,8 +49,8 @@ namespace DotNetOAuth {
/// <remarks>
/// This overload for testing purposes only.
/// </remarks>
- internal OAuthChannel(IMessageTypeProvider messageTypeProvider, IWebRequestHandler webRequestHandler)
- : base(messageTypeProvider) {
+ internal OAuthChannel(SigningBindingElementBase signingBindingElement, INonceStore store, IMessageTypeProvider messageTypeProvider, IWebRequestHandler webRequestHandler)
+ : base(messageTypeProvider, signingBindingElement, new StandardExpirationBindingElement(), new StandardReplayProtectionBindingElement(store)) {
if (webRequestHandler == null) {
throw new ArgumentNullException("webRequestHandler");
}
diff --git a/src/DotNetOAuth/OAuthMessageTypeProvider.cs b/src/DotNetOAuth/ChannelElements/OAuthMessageTypeProvider.cs
index 8e572d2..0d30ec1 100644
--- a/src/DotNetOAuth/OAuthMessageTypeProvider.cs
+++ b/src/DotNetOAuth/ChannelElements/OAuthMessageTypeProvider.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOAuth {
+namespace DotNetOAuth.ChannelElements {
using System;
using System.Collections.Generic;
using DotNetOAuth.Messages;
diff --git a/src/DotNetOAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOAuth/ChannelElements/RsaSha1SigningBindingElement.cs
new file mode 100644
index 0000000..9b25cd0
--- /dev/null
+++ b/src/DotNetOAuth/ChannelElements/RsaSha1SigningBindingElement.cs
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------
+// <copyright file="RsaSha1SigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ internal class RsaSha1SigningBindingElement : SigningBindingElementBase {
+ /// <summary>
+ /// Applies a signature to the message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ protected override void Sign(ITamperResistantOAuthMessage message) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Validates the signature on a message.
+ /// Does NOT throw an exception on failing signature verification.
+ /// </summary>
+ /// <param name="message">The message with a signature to verify.</param>
+ /// <returns>True if the signature is valid. False otherwise.</returns>
+ protected override bool IsSignatureValid(ITamperResistantOAuthMessage message) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs
new file mode 100644
index 0000000..369c844
--- /dev/null
+++ b/src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ internal abstract class SigningBindingElementBase : IChannelBindingElement {
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets the message protection provided by this binding element.
+ /// </summary>
+ public MessageProtection Protection {
+ get { return MessageProtection.TamperProtection; }
+ }
+
+ /// <summary>
+ /// Signs the outgoing message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ /// <returns>True if the message was signed. False otherwise.</returns>
+ public bool PrepareMessageForSending(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null) {
+ this.Sign(signedMessage);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Verifies the signature on an incoming message.
+ /// </summary>
+ /// <param name="message">The message whose signature should be verified.</param>
+ /// <returns>True if the signature was verified. False if the message had no signature.</returns>
+ /// <exception cref="InvalidSignatureException">Thrown if the signature is invalid.</exception>
+ public bool PrepareMessageForReceiving(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null) {
+ if (!this.IsSignatureValid(signedMessage)) {
+ throw new InvalidSignatureException(message);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Applies a signature to the message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ protected abstract void Sign(ITamperResistantOAuthMessage message);
+
+ /// <summary>
+ /// Validates the signature on a message.
+ /// Does NOT throw an exception on failing signature verification.
+ /// </summary>
+ /// <param name="message">The message with a signature to verify.</param>
+ /// <returns>True if the signature is valid. False otherwise.</returns>
+ protected abstract bool IsSignatureValid(ITamperResistantOAuthMessage message);
+ }
+}
diff --git a/src/DotNetOAuth/StandardWebRequestHandler.cs b/src/DotNetOAuth/ChannelElements/StandardWebRequestHandler.cs
index d56562b..8d0adb2 100644
--- a/src/DotNetOAuth/StandardWebRequestHandler.cs
+++ b/src/DotNetOAuth/ChannelElements/StandardWebRequestHandler.cs
@@ -4,7 +4,7 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace DotNetOAuth {
+namespace DotNetOAuth.ChannelElements {
using System;
using System.IO;
using System.Net;
diff --git a/src/DotNetOAuth/DotNetOAuth.csproj b/src/DotNetOAuth/DotNetOAuth.csproj
index 71cc127..a8673ea 100644
--- a/src/DotNetOAuth/DotNetOAuth.csproj
+++ b/src/DotNetOAuth/DotNetOAuth.csproj
@@ -66,11 +66,15 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Messaging\Bindings\NonceMemoryStore.cs" />
+ <Compile Include="ChannelElements\SigningBindingElementBase.cs" />
<Compile Include="Consumer.cs" />
- <Compile Include="IWebRequestHandler.cs" />
- <Compile Include="Messages\ITamperResistantOAuthMessage.cs" />
+ <Compile Include="ChannelElements\IWebRequestHandler.cs" />
+ <Compile Include="ChannelElements\ITamperResistantOAuthMessage.cs" />
<Compile Include="Messages\MessageBase.cs" />
<Compile Include="Messages\RequestAccessTokenMessage.cs" />
+ <Compile Include="Messaging\Bindings\INonceStore.cs" />
+ <Compile Include="Messaging\Bindings\StandardReplayProtectionBindingElement.cs" />
<Compile Include="Messaging\MessagePartAttribute.cs" />
<Compile Include="Messaging\MessageProtection.cs" />
<Compile Include="Messaging\IChannelBindingElement.cs" />
@@ -81,7 +85,7 @@
<Compile Include="Messaging\Bindings\IExpiringProtocolMessage.cs" />
<Compile Include="Messages\AccessProtectedResourcesMessage.cs" />
<Compile Include="Messages\GrantAccessTokenMessage.cs" />
- <Compile Include="Messages\IOAuthProtocolDirectedMessage.cs" />
+ <Compile Include="ChannelElements\IOAuthProtocolDirectedMessage.cs" />
<Compile Include="Messages\DirectUserToConsumerMessage.cs" />
<Compile Include="Messages\DirectUserToServiceProviderMessage.cs" />
<Compile Include="Messages\UnauthorizedRequestTokenMessage.cs" />
@@ -103,7 +107,7 @@
<Compile Include="Messaging\Reflection\MessageDictionary.cs" />
<Compile Include="Messaging\Reflection\MessagePart.cs" />
<Compile Include="Messaging\UnprotectedMessageException.cs" />
- <Compile Include="OAuthChannel.cs" />
+ <Compile Include="ChannelElements\OAuthChannel.cs" />
<Compile Include="Messaging\Response.cs" />
<Compile Include="Messaging\IProtocolMessage.cs" />
<Compile Include="Logger.cs" />
@@ -113,11 +117,12 @@
<Compile Include="Loggers\TraceLogger.cs" />
<Compile Include="Messaging\MessageScheme.cs" />
<Compile Include="Messaging\MessageTransport.cs" />
- <Compile Include="OAuthMessageTypeProvider.cs" />
+ <Compile Include="ChannelElements\OAuthMessageTypeProvider.cs" />
<Compile Include="Messaging\ProtocolException.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Messages\RequestTokenMessage.cs" />
- <Compile Include="StandardWebRequestHandler.cs" />
+ <Compile Include="ChannelElements\RsaSha1SigningBindingElement.cs" />
+ <Compile Include="ChannelElements\StandardWebRequestHandler.cs" />
<Compile Include="Util.cs" />
<Compile Include="Protocol.cs" />
<Compile Include="ServiceProvider.cs" />
diff --git a/src/DotNetOAuth/Messages/MessageBase.cs b/src/DotNetOAuth/Messages/MessageBase.cs
index e689f26..0067c2b 100644
--- a/src/DotNetOAuth/Messages/MessageBase.cs
+++ b/src/DotNetOAuth/Messages/MessageBase.cs
@@ -7,8 +7,7 @@
namespace DotNetOAuth.Messages {
using System;
using System.Collections.Generic;
- using System.Linq;
- using System.Text;
+ using DotNetOAuth.ChannelElements;
using DotNetOAuth.Messaging;
using DotNetOAuth.Messaging.Bindings;
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>