diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2012-01-29 14:32:45 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2012-01-29 14:32:45 -0800 |
commit | 5fec515095ee10b522f414a03e78f282aaf520dc (patch) | |
tree | 204c75486639c23cdda2ef38b34d7e5050a1a2e3 /src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements | |
parent | f1a4155398635a4fd9f485eec817152627682704 (diff) | |
parent | 8f4165ee515728aca3faaa26e8354a40612e85e4 (diff) | |
download | DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.zip DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.gz DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.bz2 |
Merge branch 'splitDlls'.
DNOA now builds and (in some cases) ships as many distinct assemblies.
Diffstat (limited to 'src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements')
4 files changed, 454 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/AssociateUnencryptedProviderRequest.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/AssociateUnencryptedProviderRequest.cs new file mode 100644 index 0000000..322c435 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/AssociateUnencryptedProviderRequest.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociateUnencryptedProviderRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Represents an association request received by the OpenID Provider that is sent using HTTPS and + /// otherwise communicates the shared secret in plain text. + /// </summary> + internal class AssociateUnencryptedProviderRequest : AssociateUnencryptedRequest, IAssociateRequestProvider { + /// <summary> + /// Initializes a new instance of the <see cref="AssociateUnencryptedProviderRequest"/> class. + /// </summary> + /// <param name="version">The OpenID version this message must comply with.</param> + /// <param name="providerEndpoint">The OpenID Provider endpoint.</param> + internal AssociateUnencryptedProviderRequest(Version version, Uri providerEndpoint) + : base(version, providerEndpoint) { + } + + /// <summary> + /// Creates a Provider's response to an incoming association request. + /// </summary> + /// <returns> + /// The appropriate association response message. + /// </returns> + public IProtocolMessage CreateResponseCore() { + var response = new AssociateUnencryptedResponseProvider(this.Version, this); + response.AssociationType = this.AssociationType; + return response; + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderChannel.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderChannel.cs new file mode 100644 index 0000000..5812a96 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderChannel.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProviderChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// The messaging channel for OpenID Providers. + /// </summary> + internal class OpenIdProviderChannel : OpenIdChannel { + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProviderChannel"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The OpenID Provider's association store or handle encoder.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings.</param> + internal OpenIdProviderChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) + : this(cryptoKeyStore, nonceStore, new OpenIdProviderMessageFactory(), securitySettings) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNull(securitySettings, "securitySettings"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdProviderChannel"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The association store to use.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> + /// <param name="securitySettings">The security settings.</param> + private OpenIdProviderChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) + : base(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings)) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNull(messageTypeProvider, "messageTypeProvider"); + Requires.NotNull(securitySettings, "securitySettings"); + } + + /// <summary> + /// Initializes the binding elements. + /// </summary> + /// <param name="cryptoKeyStore">The OpenID Provider's crypto key store.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings to apply. Must be an instance of either RelyingPartySecuritySettings or ProviderSecuritySettings.</param> + /// <returns> + /// An array of binding elements which may be used to construct the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(cryptoKeyStore, "cryptoKeyStore"); + Requires.NotNull(securitySettings, "securitySettings"); + Requires.NotNull(nonceStore, "nonceStore"); + + SigningBindingElement signingElement; + signingElement = new ProviderSigningBindingElement(cryptoKeyStore, securitySettings); + + var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); + + List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); + elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings, true)); + elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true)); + elements.Add(new StandardExpirationBindingElement()); + elements.Add(signingElement); + + return elements.ToArray(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderMessageFactory.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderMessageFactory.cs new file mode 100644 index 0000000..3fab06b --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/OpenIdProviderMessageFactory.cs @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdProviderMessageFactory.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// OpenID Provider message factory. + /// </summary> + internal class OpenIdProviderMessageFactory : IMessageFactory { + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + RequestBase message = null; + + // Discern the OpenID version of the message. + Protocol protocol = Protocol.V11; + string ns; + if (fields.TryGetValue(Protocol.V20.openid.ns, out ns)) { + ErrorUtilities.VerifyProtocol(string.Equals(ns, Protocol.OpenId2Namespace, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.V20.openid.ns, ns); + protocol = Protocol.V20; + } + + string mode; + if (fields.TryGetValue(protocol.openid.mode, out mode)) { + if (string.Equals(mode, protocol.Args.Mode.associate)) { + if (fields.ContainsKey(protocol.openid.dh_consumer_public)) { + message = new AssociateDiffieHellmanProviderRequest(protocol.Version, recipient.Location); + } else { + message = new AssociateUnencryptedProviderRequest(protocol.Version, recipient.Location); + } + } else if (string.Equals(mode, protocol.Args.Mode.checkid_setup) || + string.Equals(mode, protocol.Args.Mode.checkid_immediate)) { + AuthenticationRequestMode authMode = string.Equals(mode, protocol.Args.Mode.checkid_immediate) ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; + if (fields.ContainsKey(protocol.openid.identity)) { + message = new CheckIdRequest(protocol.Version, recipient.Location, authMode); + } else { + ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); + message = new SignedResponseRequest(protocol.Version, recipient.Location, authMode); + } + } else if (string.Equals(mode, protocol.Args.Mode.check_authentication)) { + message = new CheckAuthenticationRequest(protocol.Version, recipient.Location); + } else { + ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessagePartValue, protocol.openid.mode, mode); + } + } + + if (message != null) { + message.SetAsIncoming(); + } + + return message; + } + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request">The message that was sent as a request that resulted in the response.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + public IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + // OpenID Providers make no outbound requests, and thus receive no direct response messages. + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/ProviderSigningBindingElement.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/ProviderSigningBindingElement.cs new file mode 100644 index 0000000..e257e63 --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/ChannelElements/ProviderSigningBindingElement.cs @@ -0,0 +1,251 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderSigningBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// The signing binding element for OpenID Providers. + /// </summary> + internal class ProviderSigningBindingElement : SigningBindingElement { + /// <summary> + /// The association store used by Providers to look up the secrets needed for signing. + /// </summary> + private readonly IProviderAssociationStore opAssociations; + + /// <summary> + /// The security settings at the Provider. + /// Only defined when this element is instantiated to service a Provider. + /// </summary> + private readonly ProviderSecuritySettings opSecuritySettings; + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderSigningBindingElement"/> class. + /// </summary> + /// <param name="associationStore">The association store used to look up the secrets needed for signing.</param> + /// <param name="securitySettings">The security settings.</param> + internal ProviderSigningBindingElement(IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Requires.NotNull(associationStore, "associationStore"); + Requires.NotNull(securitySettings, "securitySettings"); + + this.opAssociations = associationStore; + this.opSecuritySettings = securitySettings; + } + + /// <summary> + /// Gets a value indicating whether this binding element is on a Provider channel. + /// </summary> + protected override bool IsOnProvider { + get { return true; } + } + + /// <summary> + /// Prepares a message for sending based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The message to prepare for sending.</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 override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var result = base.ProcessOutgoingMessage(message); + if (result != null) { + return result; + } + + var signedMessage = message as ITamperResistantOpenIdMessage; + if (signedMessage != null) { + Logger.Bindings.DebugFormat("Signing {0} message.", message.GetType().Name); + Association association = this.GetAssociation(signedMessage); + signedMessage.AssociationHandle = association.Handle; + signedMessage.SignedParameterOrder = this.GetSignedParameterOrder(signedMessage); + signedMessage.Signature = this.GetSignature(signedMessage, association); + return MessageProtections.TamperProtection; + } + + return null; + } + + /// <summary> + /// Gets the association to use to sign or verify a message. + /// </summary> + /// <param name="signedMessage">The message to sign or verify.</param> + /// <returns> + /// The association to use to sign or verify the message. + /// </returns> + protected override Association GetAssociation(ITamperResistantOpenIdMessage signedMessage) { + // We're on a Provider to either sign (smart/dumb) or verify a dumb signature. + bool signing = string.IsNullOrEmpty(signedMessage.Signature); + + if (signing) { + // If the RP has no replay protection, coerce use of a private association + // instead of a shared one (if security settings indicate) + // to protect the authenticating user from replay attacks. + bool forcePrivateAssociation = this.opSecuritySettings.ProtectDownlevelReplayAttacks + && IsRelyingPartyVulnerableToReplays(null, (IndirectSignedResponse)signedMessage); + + if (forcePrivateAssociation) { + if (!string.IsNullOrEmpty(signedMessage.AssociationHandle)) { + Logger.Signatures.Info("An OpenID 1.x authentication request with a shared association handle will be responded to with a private association in order to provide OP-side replay protection."); + } + + return this.GetDumbAssociationForSigning(); + } else { + return this.GetSpecificAssociation(signedMessage) ?? this.GetDumbAssociationForSigning(); + } + } else { + return this.GetSpecificAssociation(signedMessage); + } + } + + /// <summary> + /// Gets a specific association referenced in a given message's association handle. + /// </summary> + /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param> + /// <returns> + /// The referenced association; or <c>null</c> if such an association cannot be found. + /// </returns> + protected override Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage) { + Association association = null; + + if (!string.IsNullOrEmpty(signedMessage.AssociationHandle)) { + IndirectSignedResponse indirectSignedMessage = signedMessage as IndirectSignedResponse; + + // Since we have an association handle, we're either signing with a smart association, + // or verifying a dumb one. + bool signing = string.IsNullOrEmpty(signedMessage.Signature); + bool isPrivateAssociation = !signing; + association = this.opAssociations.Deserialize(signedMessage, isPrivateAssociation, signedMessage.AssociationHandle); + if (association == null) { + // There was no valid association with the requested handle. + // Let's tell the RP to forget about that association. + signedMessage.InvalidateHandle = signedMessage.AssociationHandle; + signedMessage.AssociationHandle = null; + } + } + + return association; + } + + /// <summary> + /// Gets a private Provider association used for signing messages in "dumb" mode. + /// </summary> + /// <returns>An existing or newly created association.</returns> + protected override Association GetDumbAssociationForSigning() { + // If no assoc_handle was given or it was invalid, the only thing + // left to do is sign a message using a 'dumb' mode association. + Protocol protocol = Protocol.Default; + Association association = HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opAssociations, this.opSecuritySettings); + return association; + } + + /// <summary> + /// Verifies the signature by unrecognized handle. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="signedMessage">The signed message.</param> + /// <param name="protectionsApplied">The protections applied.</param> + /// <returns> + /// The applied protections. + /// </returns> + protected override MessageProtections VerifySignatureByUnrecognizedHandle(IProtocolMessage message, ITamperResistantOpenIdMessage signedMessage, MessageProtections protectionsApplied) { + // If we're on the Provider, then the RP sent us a check_auth with a signature + // we don't have an association for. (It may have expired, or it may be a faulty RP). + throw new InvalidSignatureException(message); + } + + /// <summary> + /// Determines whether the relying party sending an authentication request is + /// vulnerable to replay attacks. + /// </summary> + /// <param name="request">The request message from the Relying Party. Useful, but may be null for conservative estimate results.</param> + /// <param name="response">The response message to be signed.</param> + /// <returns> + /// <c>true</c> if the relying party is vulnerable; otherwise, <c>false</c>. + /// </returns> + private static bool IsRelyingPartyVulnerableToReplays(SignedResponseRequest request, IndirectSignedResponse response) { + Requires.NotNull(response, "response"); + + // OpenID 2.0 includes replay protection as part of the protocol. + if (response.Version.Major >= 2) { + return false; + } + + // This library's RP may be on the remote end, and may be using 1.x merely because + // discovery on the Claimed Identifier suggested this was a 1.x OP. + // Since this library's RP has a built-in request_nonce parameter for replay + // protection, we'll allow for that. + var returnToArgs = HttpUtility.ParseQueryString(response.ReturnTo.Query); + if (!string.IsNullOrEmpty(returnToArgs[ReturnToNonceBindingElement.NonceParameter])) { + return false; + } + + // If the OP endpoint _AND_ RP return_to URL uses HTTPS then no one + // can steal and replay the positive assertion. + // We can only ascertain this if the request message was handed to us + // so we know what our own OP endpoint is. If we don't have a request + // message, then we'll default to assuming it's insecure. + if (request != null) { + if (request.Recipient.IsTransportSecure() && response.Recipient.IsTransportSecure()) { + return false; + } + } + + // Nothing left to protect against replays. RP is vulnerable. + return true; + } + + /// <summary> + /// Gets the value to use for the openid.signed parameter. + /// </summary> + /// <param name="signedMessage">The signable message.</param> + /// <returns> + /// A comma-delimited list of parameter names, omitting the 'openid.' prefix, that determines + /// the inclusion and order of message parts that will be signed. + /// </returns> + private string GetSignedParameterOrder(ITamperResistantOpenIdMessage signedMessage) { + Requires.ValidState(this.Channel != null); + Requires.NotNull(signedMessage, "signedMessage"); + + Protocol protocol = Protocol.Lookup(signedMessage.Version); + + MessageDescription description = this.Channel.MessageDescriptions.Get(signedMessage); + var signedParts = from part in description.Mapping.Values + where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 + && part.GetValue(signedMessage) != null + select part.Name; + string prefix = Protocol.V20.openid.Prefix; + ErrorUtilities.VerifyInternal(signedParts.All(name => name.StartsWith(prefix, StringComparison.Ordinal)), "All signed message parts must start with 'openid.'."); + + if (this.opSecuritySettings.SignOutgoingExtensions) { + // Tack on any ExtraData parameters that start with 'openid.'. + List<string> extraSignedParameters = new List<string>(signedMessage.ExtraData.Count); + foreach (string key in signedMessage.ExtraData.Keys) { + if (key.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)) { + extraSignedParameters.Add(key); + } else { + Logger.Signatures.DebugFormat("The extra parameter '{0}' will not be signed because it does not start with 'openid.'.", key); + } + } + signedParts = signedParts.Concat(extraSignedParameters); + } + + int skipLength = prefix.Length; + string signedFields = string.Join(",", signedParts.Select(name => name.Substring(skipLength)).ToArray()); + return signedFields; + } + } +} |