diff options
11 files changed, 134 insertions, 50 deletions
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs index 0bb2195..7a96718 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs @@ -72,6 +72,11 @@ namespace DotNetOpenAuth.Test.Mocks { return accessor.ReadFromResponseInternal(response); } + protected override void VerifyMessageAfterReceiving(IProtocolMessage message) { + Channel_Accessor accessor = Channel_Accessor.AttachShadow(this.wrappedChannel); + accessor.VerifyMessageAfterReceiving(message); + } + /// <summary> /// Spoof HTTP request information for signing/verification purposes. /// </summary> diff --git a/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs b/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs index d550514..11d7e67 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs @@ -17,7 +17,7 @@ namespace DotNetOpenAuth.Test.Mocks { MessageProtections IChannelBindingElement.Protection { get { return MessageProtections.ReplayProtection; } } - + /// <summary> /// Gets or sets the channel that this binding element belongs to. /// </summary> diff --git a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs index fa769ec..887bd69 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs @@ -102,9 +102,9 @@ namespace DotNetOpenAuth.Test.OpenId { Assert.IsNotNull(request); var response = new PositiveAssertionResponse(request); op.Channel.Send(response); - var checkauth = op.Channel.ReadFromRequest<CheckAuthenticationRequest>(); - var checkauthResponse = new CheckAuthenticationResponse(checkauth); - checkauthResponse.IsValid = true; // TODO: how do we establish that the signature is good? + var checkauthRequest = op.Channel.ReadFromRequest<CheckAuthenticationRequest>(); + var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest); + checkauthResponse.IsValid = checkauthRequest.IsValid; op.Channel.Send(checkauthResponse); }); coordinator.Run(); diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 4cfa0f4..46b932d 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -641,6 +641,35 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Verifies the integrity and applicability of an incoming message. + /// </summary> + /// <param name="message">The message just received.</param> + /// <exception cref="ProtocolException"> + /// Thrown when the message is somehow invalid. + /// This can be due to tampering, replay attack or expiration, among other things. + /// </exception> + protected virtual void VerifyMessageAfterReceiving(IProtocolMessage message) { + Debug.Assert(message != null, "message == null"); + + MessageProtections appliedProtection = MessageProtections.None; + foreach (IChannelBindingElement bindingElement in this.bindingElements.Reverse<IChannelBindingElement>()) { + if (bindingElement.PrepareMessageForReceiving(message)) { + appliedProtection |= bindingElement.Protection; + } + } + + // Ensure that the message's protection requirements have been satisfied. + if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) { + throw new UnprotectedMessageException(message, appliedProtection); + } + + // We do NOT verify that all required message parts are present here... the + // message deserializer did for us. It would be too late to do it here since + // they might look initialized by the time we have an IProtocolMessage instance. + message.EnsureValidMessage(); + } + + /// <summary> /// Verifies that all required message parts are initialized to values /// prior to sending the message to a remote party. /// </summary> @@ -749,34 +778,5 @@ namespace DotNetOpenAuth.Messaging { // Now put the protection ones in the right order. return -((int)protection1).CompareTo((int)protection2); // descending flag ordinal order } - - /// <summary> - /// Verifies the integrity and applicability of an incoming message. - /// </summary> - /// <param name="message">The message just received.</param> - /// <exception cref="ProtocolException"> - /// Thrown when the message is somehow invalid. - /// This can be due to tampering, replay attack or expiration, among other things. - /// </exception> - private void VerifyMessageAfterReceiving(IProtocolMessage message) { - Debug.Assert(message != null, "message == null"); - - MessageProtections appliedProtection = MessageProtections.None; - foreach (IChannelBindingElement bindingElement in this.bindingElements.Reverse<IChannelBindingElement>()) { - if (bindingElement.PrepareMessageForReceiving(message)) { - appliedProtection |= bindingElement.Protection; - } - } - - // Ensure that the message's protection requirements have been satisfied. - if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) { - throw new UnprotectedMessageException(message, appliedProtection); - } - - // We do NOT verify that all required message parts are present here... the - // message deserializer did for us. It would be too late to do it here since - // they might look initialized by the time we have an IProtocolMessage instance. - message.EnsureValidMessage(); - } } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs index 36c823d..7337760 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -38,6 +38,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { get { return MessageProtections.TamperProtection; } } + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + public Channel Channel { get; set; } + #endregion #region ITamperProtectionChannelBindingElement members @@ -65,11 +70,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { #region IChannelBindingElement Methods /// <summary> - /// Gets or sets the channel that this binding element belongs to. - /// </summary> - public Channel Channel { get; set; } - - /// <summary> /// Signs the outgoing message. /// </summary> /// <param name="message">The message to sign.</param> diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs index 77d7c1f..f669a2b 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs @@ -13,6 +13,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Messages; /// <summary> /// A channel that knows how to send and receive OpenID messages. @@ -77,6 +78,29 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { } /// <summary> + /// Verifies the integrity and applicability of an incoming message. + /// </summary> + /// <param name="message">The message just received.</param> + /// <exception cref="ProtocolException"> + /// Thrown when the message is somehow invalid, except for check_authentication messages. + /// This can be due to tampering, replay attack or expiration, among other things. + /// </exception> + protected override void VerifyMessageAfterReceiving(IProtocolMessage message) { + var checkAuthRequest = message as CheckAuthenticationRequest; + if (checkAuthRequest != null) { + IndirectSignedResponse originalResponse = new IndirectSignedResponse(checkAuthRequest); + try { + base.VerifyMessageAfterReceiving(originalResponse); + checkAuthRequest.IsValid = true; + } catch (ProtocolException) { + checkAuthRequest.IsValid = false; + } + } else { + base.VerifyMessageAfterReceiving(message); + } + } + + /// <summary> /// Prepares an HTTP request that carries a given message. /// </summary> /// <param name="request">The message to send.</param> diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs index bf4b428..64b3b60 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs @@ -115,6 +115,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { } } else { ErrorUtilities.VerifyInternal(this.Channel != null, "Cannot verify private association signature because we don't have a channel."); + // We did not recognize the association the provider used to sign the message. // Ask the provider to check the signature then. var checkSignatureRequest = new CheckAuthenticationRequest((IndirectSignedResponse)signedMessage); @@ -193,9 +194,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { Protocol protocol = Protocol.Lookup(signedMessage.ProtocolVersion); MessageDictionary dictionary = new MessageDictionary(signedMessage); var parametersToSign = from name in signedMessage.SignedParameterOrder.Split(',') - let prefixedName = Protocol.V20.openid.Prefix + name - let alteredValue = name == protocol.openidnp.mode && dictionary[prefixedName] == protocol.Args.Mode.check_authentication ? protocol.Args.Mode.id_res : dictionary[prefixedName] - select new KeyValuePair<string, string>(prefixedName, alteredValue); + let prefixedName = Protocol.V20.openid.Prefix + name + select new KeyValuePair<string, string>(prefixedName, dictionary[prefixedName]); byte[] dataToSign = KeyValueFormEncoding.GetBytes(parametersToSign); return Convert.ToBase64String(association.Sign(dataToSign)); diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs index f8773a0..71da4c8 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.OpenId.ChannelElements; /// <summary> @@ -32,12 +33,43 @@ namespace DotNetOpenAuth.OpenId.Messages { } /// <summary> - /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class. + /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class + /// based on the contents of some signed message whose signature must be verified. /// </summary> /// <param name="message">The message whose signature should be verified.</param> internal CheckAuthenticationRequest(IndirectSignedResponse message) : base(message.ProtocolVersion, message.ProviderEndpoint, GetProtocolConstant(message.ProtocolVersion, p => p.Args.Mode.check_authentication), MessageTransport.Direct) { - // TODO: code here to copy data from message into this one. + // Copy all message parts from the id_res message into this one, + // except for the openid.mode parameter. + MessageDictionary checkPayload = new MessageDictionary(message); + MessageDictionary thisPayload = new MessageDictionary(this); + foreach (var pair in checkPayload) { + if (!string.Equals(pair.Key, this.Protocol.openid.mode)) { + thisPayload[pair.Key] = pair.Value; + } + } } + + /// <summary> + /// Gets or sets a value indicating whether the signature being verified by this request + /// is in fact valid. + /// </summary> + /// <value><c>true</c> if the signature is valid; otherwise, <c>false</c>.</value> + /// <remarks> + /// This property is automatically set as the message is received by the channel's + /// signing binding element. + /// </remarks> + internal bool IsValid { get; set; } + + /// <summary> + /// Gets or sets the ReturnTo that existed in the original signed message. + /// </summary> + /// <remarks> + /// This exists strictly for convenience in recreating the <see cref="IndirectSignedResponse"/> + /// message. + /// </remarks> + [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false)] + [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")] + internal Uri ReturnTo { get; set; } } } diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs index 140fc63..2aaff15 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs @@ -20,7 +20,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <param name="request">The request that caused this response message to be constructed.</param> /// <param name="mode">The value of the openid.mode parameter.</param> - protected IndirectResponseBase(CheckIdRequest request, string mode) + protected IndirectResponseBase(SignedResponseRequest request, string mode) : base(GetVersion(request), GetReturnTo(request), mode, MessageTransport.Indirect) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); @@ -43,7 +43,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <summary> /// Gets the originating request message, if applicable. /// </summary> - protected CheckIdRequest OriginatingRequest { get; private set; } + protected SignedResponseRequest OriginatingRequest { get; private set; } /// <summary> /// Gets the <see cref="IProtocolMessage.ProtocolVersion"/> property of a message. @@ -68,7 +68,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/> /// instead of a <see cref="NullReferenceException"/>. /// </remarks> - private static Uri GetReturnTo(CheckIdRequest message) { + private static Uri GetReturnTo(SignedResponseRequest message) { ErrorUtilities.VerifyArgumentNotNull(message, "message"); ErrorUtilities.VerifyProtocol(message.ReturnTo != null, OpenIdStrings.ReturnToRequiredForResponse); return message.ReturnTo; diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs index 8883acb..2c5c150 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs @@ -39,7 +39,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="request"> /// The authentication request that caused this assertion to be generated. /// </param> - internal IndirectSignedResponse(CheckIdRequest request) + internal IndirectSignedResponse(SignedResponseRequest request) : base(request, Protocol.Lookup(GetVersion(request)).Args.Mode.id_res) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); @@ -50,6 +50,24 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <summary> /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class + /// in order to perform signature verification at the Provider. + /// </summary> + /// <param name="previouslySignedMessage">The previously signed message.</param> + internal IndirectSignedResponse(CheckAuthenticationRequest previouslySignedMessage) + : base(GetVersion(previouslySignedMessage), previouslySignedMessage.ReturnTo, Protocol.Lookup(GetVersion(previouslySignedMessage)).Args.Mode.id_res) { + // Copy all message parts from the check_authentication message into this one, + // except for the openid.mode parameter. + MessageDictionary checkPayload = new MessageDictionary(previouslySignedMessage); + MessageDictionary thisPayload = new MessageDictionary(this); + foreach (var pair in checkPayload) { + if (!string.Equals(pair.Key, this.Protocol.openid.mode)) { + thisPayload[pair.Key] = pair.Value; + } + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class /// for unsolicited assertions. /// </summary> /// <param name="version">The OpenID version to use.</param> diff --git a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs index 98821ac..6786a44 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs @@ -30,11 +30,16 @@ namespace DotNetOpenAuth.OpenId.Messages { #pragma warning restore 0414 /// <summary> - /// Backing store for the <see cref="Incoming"/> properties. + /// Backing store for the <see cref="Incoming"/> property. /// </summary> private bool incoming; /// <summary> + /// Backing store for the <see cref="ExtraData"/> property. + /// </summary> + private Dictionary<string, string> extraData = new Dictionary<string, string>(); + + /// <summary> /// Initializes a new instance of the <see cref="RequestBase"/> class. /// </summary> /// <param name="version">The OpenID version this message must comply with.</param> @@ -114,11 +119,11 @@ namespace DotNetOpenAuth.OpenId.Messages { public MessageTransport Transport { get; private set; } /// <summary> - /// Gets the extra, non-OAuth parameters included in the message. + /// Gets the extra parameters included in the message. /// </summary> /// <value>An empty dictionary.</value> public IDictionary<string, string> ExtraData { - get { return EmptyDictionary<string, string>.Instance; } + get { return this.extraData; } } /// <summary> |