1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
//-----------------------------------------------------------------------
// <copyright file="StandardReplayProtectionBindingElement.cs" company="Outercurve Foundation">
// Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Messaging.Bindings {
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Validation;
/// <summary>
/// A binding element that checks/verifies a nonce message part.
/// </summary>
internal class StandardReplayProtectionBindingElement : IChannelBindingElement {
private static readonly Task<MessageProtections?> NullTask = Task.FromResult<MessageProtections?>(null);
private static readonly Task<MessageProtections?> CompletedReplayProtectionTask = Task.FromResult<MessageProtections?>(MessageProtections.ReplayProtection);
/// <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>
/// 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)
: this(nonceStore, false) {
}
/// <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>
/// <param name="allowEmptyNonces">A value indicating whether zero-length nonces will be allowed.</param>
internal StandardReplayProtectionBindingElement(INonceStore nonceStore, bool allowEmptyNonces) {
Requires.NotNull(nonceStore, "nonceStore");
this.nonceStore = nonceStore;
this.AllowZeroLengthNonce = allowEmptyNonces;
}
#region IChannelBindingElement Properties
/// <summary>
/// Gets the protection that this binding element provides messages.
/// </summary>
public MessageProtections Protection {
get { return MessageProtections.ReplayProtection; }
}
/// <summary>
/// Gets or sets the channel that this binding element belongs to.
/// </summary>
public Channel Channel { get; set; }
#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!");
}
}
/// <summary>
/// Gets or sets a value indicating whether empty nonces are allowed.
/// </summary>
/// <value>Default is <c>false</c>.</value>
internal bool AllowZeroLengthNonce { get; set; }
#region IChannelBindingElement Methods
/// <summary>
/// Applies a nonce to the message.
/// </summary>
/// <param name="message">The message to apply replay protection to.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// 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.
/// </returns>
public Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) {
IReplayProtectedProtocolMessage nonceMessage = message as IReplayProtectedProtocolMessage;
if (nonceMessage != null) {
nonceMessage.Nonce = this.GenerateUniqueFragment();
return CompletedReplayProtectionTask;
}
return NullTask;
}
/// <summary>
/// Verifies that the nonce in an incoming message has not been seen before.
/// </summary>
/// <param name="message">The incoming message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// 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.
/// </returns>
/// <exception cref="ReplayedMessageException">Thrown when the nonce check revealed a replayed message.</exception>
/// <remarks>
/// Implementations that provide message protection must honor the
/// <see cref="MessagePartAttribute.RequiredProtection" /> properties where applicable.
/// </remarks>
public Task<MessageProtections?> 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
/// <summary>
/// Generates a string of random characters for use as a nonce.
/// </summary>
/// <returns>The nonce string.</returns>
private string GenerateUniqueFragment() {
return MessagingUtilities.GetRandomString(this.nonceLength, AllowedCharacters);
}
}
}
|