FxCop fixes.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / ChannelElements / ReturnToSignatureBindingElement.cs
blob0e8e895a4a1c9edb981c31b9c6f885e4266f9316
1 //-----------------------------------------------------------------------
2 // <copyright file="ReturnToSignatureBindingElement.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.OpenId.ChannelElements {
8 using System;
9 using System.Collections.Generic;
10 using System.Collections.Specialized;
11 using System.Security.Cryptography;
12 using System.Web;
13 using DotNetOpenAuth.Messaging;
14 using DotNetOpenAuth.OpenId.Messages;
16 /// <summary>
17 /// This binding element signs a Relying Party's openid.return_to parameter
18 /// so that upon return, it can verify that it hasn't been tampered with.
19 /// </summary>
20 /// <remarks>
21 /// <para>Since Providers can send unsolicited assertions, not all openid.return_to
22 /// values will be signed. But those that are signed will be validated, and
23 /// any invalid or missing signatures will cause this library to not trust
24 /// the parameters in the return_to URL.</para>
25 /// <para>In the messaging stack, this binding element looks like an ordinary
26 /// transform-type of binding element rather than a protection element,
27 /// due to its required order in the channel stack and that it doesn't sign
28 /// anything except a particular message part.</para>
29 /// </remarks>
30 internal class ReturnToSignatureBindingElement : IChannelBindingElement, IDisposable {
31 /// <summary>
32 /// The optimal length for a private secret used for signing using the HMACSHA256 class.
33 /// </summary>
34 /// <remarks>
35 /// The 64-byte length is optimized for highest security when used with HMACSHA256.
36 /// See HMACSHA256.HMACSHA256(byte[]) documentation for more information.
37 /// </remarks>
38 internal static readonly int OptimalPrivateSecretLength = 64;
40 /// <summary>
41 /// The name of the callback parameter we'll tack onto the return_to value
42 /// to store our signature on the return_to parameter.
43 /// </summary>
44 private const string ReturnToSignatureParameterName = "dnoi.return_to_sig";
46 /// <summary>
47 /// The hashing algorithm used to generate the private signature on the return_to parameter.
48 /// </summary>
49 private HashAlgorithm signingHasher;
51 /// <summary>
52 /// Initializes a new instance of the <see cref="ReturnToSignatureBindingElement"/> class.
53 /// </summary>
54 /// <param name="secretStore">The secret store from which to retrieve the secret used for signing.</param>
55 internal ReturnToSignatureBindingElement(IPrivateSecretStore secretStore) {
56 ErrorUtilities.VerifyArgumentNotNull(secretStore, "secretStore");
57 ErrorUtilities.VerifyInternal(secretStore.PrivateSecret != null, "Private secret should have been set already.");
59 if (secretStore.PrivateSecret.Length < OptimalPrivateSecretLength) {
60 Logger.WarnFormat("For best security, the optimal length of a private signing secret is {0} bytes, but the secret we have is only {1} bytes.", OptimalPrivateSecretLength, secretStore.PrivateSecret.Length);
63 this.signingHasher = new HMACSHA256(secretStore.PrivateSecret);
66 #region IChannelBindingElement Members
68 /// <summary>
69 /// Gets or sets the channel that this binding element belongs to.
70 /// </summary>
71 /// <value></value>
72 /// <remarks>
73 /// This property is set by the channel when it is first constructed.
74 /// </remarks>
75 public Channel Channel { get; set; }
77 /// <summary>
78 /// Gets the protection offered (if any) by this binding element.
79 /// </summary>
80 /// <value><see cref="MessageProtections.None"/></value>
81 public MessageProtections Protection {
82 get { return MessageProtections.None; }
85 /// <summary>
86 /// Prepares a message for sending based on the rules of this channel binding element.
87 /// </summary>
88 /// <param name="message">The message to prepare for sending.</param>
89 /// <returns>
90 /// True if the <paramref name="message"/> applied to this binding element
91 /// and the operation was successful. False otherwise.
92 /// </returns>
93 /// <remarks>
94 /// Implementations that provide message protection must honor the
95 /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
96 /// </remarks>
97 public bool PrepareMessageForSending(IProtocolMessage message) {
98 SignedResponseRequest request = message as SignedResponseRequest;
99 if (request != null) {
100 string signature = this.GetReturnToSignature(request.ReturnTo);
101 request.AddReturnToArguments(ReturnToSignatureParameterName, signature);
102 return true;
105 return false;
108 /// <summary>
109 /// Performs any transformation on an incoming message that may be necessary and/or
110 /// validates an incoming message based on the rules of this channel binding element.
111 /// </summary>
112 /// <param name="message">The incoming message to process.</param>
113 /// <returns>
114 /// True if the <paramref name="message"/> applied to this binding element
115 /// and the operation was successful. False if the operation did not apply to this message.
116 /// </returns>
117 /// <exception cref="ProtocolException">
118 /// Thrown when the binding element rules indicate that this message is invalid and should
119 /// NOT be processed.
120 /// </exception>
121 /// <remarks>
122 /// Implementations that provide message protection must honor the
123 /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
124 /// </remarks>
125 public bool PrepareMessageForReceiving(IProtocolMessage message) {
126 IndirectSignedResponse response = message as IndirectSignedResponse;
128 if (response != null) {
129 // We can't use response.GetReturnToArgument(string) because that relies
130 // on us already having validated this signature.
131 NameValueCollection returnToParameters = HttpUtility.ParseQueryString(response.ReturnTo.Query);
133 // Set the safety flag showing whether the return_to url had a valid signature.
134 string expected = this.GetReturnToSignature(response.ReturnTo);
135 string actual = returnToParameters[ReturnToSignatureParameterName];
136 actual = OpenIdUtilities.FixDoublyUriDecodedBase64String(actual);
137 response.ReturnToParametersSignatureValidated = actual == expected;
138 if (!response.ReturnToParametersSignatureValidated) {
139 Logger.WarnFormat("The return_to signature failed verification.");
142 return true;
145 return false;
148 #endregion
150 #region IDisposable Members
152 /// <summary>
153 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
154 /// </summary>
155 public void Dispose() {
156 this.Dispose(true);
157 GC.SuppressFinalize(this);
160 /// <summary>
161 /// Releases unmanaged and - optionally - managed resources
162 /// </summary>
163 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
164 protected virtual void Dispose(bool disposing) {
165 if (disposing) {
166 IDisposable hasher = this.signingHasher as IDisposable;
167 if (hasher != null) {
168 hasher.Dispose();
173 #endregion
175 /// <summary>
176 /// Gets the return to signature.
177 /// </summary>
178 /// <param name="returnTo">The return to.</param>
179 /// <returns>The generated signature.</returns>
180 /// <remarks>
181 /// Only the parameters in the return_to URI are signed, rather than the base URI
182 /// itself, in order that OPs that might change the return_to's implicit port :80 part
183 /// or other minor changes do not invalidate the signature.
184 /// </remarks>
185 private string GetReturnToSignature(Uri returnTo) {
186 ErrorUtilities.VerifyArgumentNotNull(returnTo, "returnTo");
188 // Assemble the dictionary to sign, taking care to remove the signature itself
189 // in order to accurately reproduce the original signature (which of course didn't include
190 // the signature).
191 // Also we need to sort the dictionary's keys so that we sign in the same order as we did
192 // the last time.
193 var returnToParameters = HttpUtility.ParseQueryString(returnTo.Query).ToDictionary();
194 returnToParameters.Remove(ReturnToSignatureParameterName);
195 var sortedReturnToParameters = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
196 foreach (var pair in returnToParameters) {
197 sortedReturnToParameters.Add(pair.Key, pair.Value);
200 Logger.DebugFormat("ReturnTo signed data: {0}{1}", Environment.NewLine, sortedReturnToParameters.ToStringDeferred());
202 // Sign the parameters.
203 byte[] bytesToSign = KeyValueFormEncoding.GetBytes(sortedReturnToParameters);
204 byte[] signature = this.signingHasher.ComputeHash(bytesToSign);
205 string signatureBase64 = Convert.ToBase64String(signature);
206 return signatureBase64;