summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs386
1 files changed, 386 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs
new file mode 100644
index 0000000..d9a0e50
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs
@@ -0,0 +1,386 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdChannel.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.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Messages;
+ using DotNetOpenAuth.OpenId.Provider;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A channel that knows how to send and receive OpenID messages.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class OpenIdChannel : Channel {
+ /// <summary>
+ /// The HTTP Content-Type to use in Key-Value Form responses.
+ /// </summary>
+ /// <remarks>
+ /// OpenID 2.0 section 5.1.2 says this SHOULD be text/plain. But this value
+ /// does not prevent free hosters like GoDaddy from tacking on their ads
+ /// to the end of the direct response, corrupting the data. So we deviate
+ /// from the spec a bit here to improve the story for free Providers.
+ /// </remarks>
+ internal const string KeyValueFormContentType = "application/x-openid-kvf";
+
+ /// <summary>
+ /// The encoder that understands how to read and write Key-Value Form.
+ /// </summary>
+ private KeyValueFormEncoding keyValueForm = new KeyValueFormEncoding();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
+ /// for use by a Relying Party.
+ /// </summary>
+ /// <param name="cryptoKeyStore">The association store to use.</param>
+ /// <param name="nonceStore">The nonce store to use.</param>
+ /// <param name="securitySettings">The security settings to apply.</param>
+ internal OpenIdChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings)
+ : this(cryptoKeyStore, nonceStore, new OpenIdMessageFactory(), securitySettings, false) {
+ Contract.Requires<ArgumentNullException>(securitySettings != null);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
+ /// for use by a Provider.
+ /// </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 OpenIdChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings)
+ : this(cryptoKeyStore, nonceStore, new OpenIdMessageFactory(), securitySettings) {
+ Contract.Requires<ArgumentNullException>(cryptoKeyStore != null);
+ Contract.Requires<ArgumentNullException>(securitySettings != null);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
+ /// for use by a Relying Party.
+ /// </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 to apply.</param>
+ /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param>
+ private OpenIdChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) :
+ this(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings, nonVerifying)) {
+ Contract.Requires<ArgumentNullException>(messageTypeProvider != null);
+ Contract.Requires<ArgumentNullException>(securitySettings != null);
+ Contract.Requires<ArgumentException>(!nonVerifying || securitySettings is RelyingPartySecuritySettings);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
+ /// for use by a Provider.
+ /// </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 OpenIdChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) :
+ this(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings)) {
+ Contract.Requires<ArgumentNullException>(cryptoKeyStore != null);
+ Contract.Requires<ArgumentNullException>(messageTypeProvider != null);
+ Contract.Requires<ArgumentNullException>(securitySettings != null);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdChannel"/> class.
+ /// </summary>
+ /// <param name="messageTypeProvider">A class prepared to analyze incoming messages and indicate what concrete
+ /// message types can deserialize from it.</param>
+ /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param>
+ private OpenIdChannel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements)
+ : base(messageTypeProvider, bindingElements) {
+ Contract.Requires<ArgumentNullException>(messageTypeProvider != null);
+
+ // Customize the binding element order, since we play some tricks for higher
+ // security and backward compatibility with older OpenID versions.
+ var outgoingBindingElements = new List<IChannelBindingElement>(bindingElements);
+ var incomingBindingElements = new List<IChannelBindingElement>(bindingElements);
+ incomingBindingElements.Reverse();
+
+ // Customize the order of the incoming elements by moving the return_to elements in front.
+ var backwardCompatibility = incomingBindingElements.OfType<BackwardCompatibilityBindingElement>().SingleOrDefault();
+ var returnToSign = incomingBindingElements.OfType<ReturnToSignatureBindingElement>().SingleOrDefault();
+ if (backwardCompatibility != null) {
+ incomingBindingElements.MoveTo(0, backwardCompatibility);
+ }
+ if (returnToSign != null) {
+ // Yes, this is intentionally, shifting the backward compatibility
+ // binding element to second position.
+ incomingBindingElements.MoveTo(0, returnToSign);
+ }
+
+ this.CustomizeBindingElementOrder(outgoingBindingElements, incomingBindingElements);
+
+ // Change out the standard web request handler to reflect the standard
+ // OpenID pattern that outgoing web requests are to unknown and untrusted
+ // servers on the Internet.
+ this.WebRequestHandler = new UntrustedWebRequestHandler();
+ }
+
+ /// <summary>
+ /// A value indicating whether the channel is set up
+ /// with no functional security binding elements.
+ /// </summary>
+ /// <returns>A new <see cref="OpenIdChannel"/> instance that will not perform verification on incoming messages or apply any security to outgoing messages.</returns>
+ /// <remarks>
+ /// <para>A value of <c>true</c> allows the relying party to preview incoming
+ /// messages without invalidating nonces or checking signatures.</para>
+ /// <para>Setting this to <c>true</c> poses a great security risk and is only
+ /// present to support the <see cref="OpenIdAjaxTextBox"/> which needs to preview
+ /// messages, and will validate them later.</para>
+ /// </remarks>
+ internal static OpenIdChannel CreateNonVerifyingChannel() {
+ Contract.Ensures(Contract.Result<OpenIdChannel>() != null);
+
+ return new OpenIdChannel(null, null, new OpenIdMessageFactory(), new RelyingPartySecuritySettings(), true);
+ }
+
+ /// <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 ProcessIncomingMessage(IProtocolMessage message) {
+ var checkAuthRequest = message as CheckAuthenticationRequest;
+ if (checkAuthRequest != null) {
+ IndirectSignedResponse originalResponse = new IndirectSignedResponse(checkAuthRequest, this);
+ try {
+ base.ProcessIncomingMessage(originalResponse);
+ checkAuthRequest.IsValid = true;
+ } catch (ProtocolException) {
+ checkAuthRequest.IsValid = false;
+ }
+ } else {
+ base.ProcessIncomingMessage(message);
+ }
+
+ // Convert an OpenID indirect error message, which we never expect
+ // between two good OpenID implementations, into an exception.
+ // We don't process DirectErrorResponse because associate negotiations
+ // commonly get a derivative of that message type and handle it.
+ var errorMessage = message as IndirectErrorResponse;
+ if (errorMessage != null) {
+ string exceptionMessage = string.Format(
+ CultureInfo.CurrentCulture,
+ OpenIdStrings.IndirectErrorFormattedMessage,
+ errorMessage.ErrorMessage,
+ errorMessage.Contact,
+ errorMessage.Reference);
+ throw new ProtocolException(exceptionMessage, message);
+ }
+ }
+
+ /// <summary>
+ /// Prepares an HTTP request that carries a given message.
+ /// </summary>
+ /// <param name="request">The message to send.</param>
+ /// <returns>
+ /// The <see cref="HttpWebRequest"/> prepared to send the request.
+ /// </returns>
+ protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) {
+ return this.InitializeRequestAsPost(request);
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response.
+ /// </summary>
+ /// <param name="response">The response that is anticipated to contain an protocol message.</param>
+ /// <returns>
+ /// The deserialized message parts, if found. Null otherwise.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
+ protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) {
+ try {
+ return this.keyValueForm.GetDictionary(response.ResponseStream);
+ } catch (FormatException ex) {
+ throw ErrorUtilities.Wrap(ex, ex.Message);
+ }
+ }
+
+ /// <summary>
+ /// Called when receiving a direct response message, before deserialization begins.
+ /// </summary>
+ /// <param name="response">The HTTP direct response.</param>
+ /// <param name="message">The newly instantiated message, prior to deserialization.</param>
+ protected override void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) {
+ base.OnReceivingDirectResponse(response, message);
+
+ // Verify that the expected HTTP status code was used for the message,
+ // per OpenID 2.0 section 5.1.2.2.
+ // Note: The v1.1 spec doesn't require 400 responses for some error messages
+ if (message.Version.Major >= 2) {
+ var httpDirectResponse = message as IHttpDirectResponse;
+ if (httpDirectResponse != null) {
+ ErrorUtilities.VerifyProtocol(
+ httpDirectResponse.HttpStatusCode == response.Status,
+ MessagingStrings.UnexpectedHttpStatusCode,
+ (int)httpDirectResponse.HttpStatusCode,
+ (int)response.Status);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream where the fields
+ /// are sent in the response stream in querystring style.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <returns>
+ /// The pending user agent redirect based message to be sent as an HttpResponse.
+ /// </returns>
+ /// <remarks>
+ /// This method implements spec V1.0 section 5.3.
+ /// </remarks>
+ protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
+ var messageAccessor = this.MessageDescriptions.GetAccessor(response);
+ var fields = messageAccessor.Serialize();
+ byte[] keyValueEncoding = KeyValueFormEncoding.GetBytes(fields);
+
+ OutgoingWebResponse preparedResponse = new OutgoingWebResponse();
+ preparedResponse.Headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType);
+ preparedResponse.OriginalMessage = response;
+ preparedResponse.ResponseStream = new MemoryStream(keyValueEncoding);
+
+ IHttpDirectResponse httpMessage = response as IHttpDirectResponse;
+ if (httpMessage != null) {
+ preparedResponse.Status = httpMessage.HttpStatusCode;
+ }
+
+ return preparedResponse;
+ }
+
+ /// <summary>
+ /// Gets the direct response of a direct HTTP request.
+ /// </summary>
+ /// <param name="webRequest">The web request.</param>
+ /// <returns>The response to the web request.</returns>
+ /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception>
+ protected override IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) {
+ IncomingWebResponse response = this.WebRequestHandler.GetResponse(webRequest, DirectWebRequestOptions.AcceptAllHttpResponses);
+
+ // Filter the responses to the allowable set of HTTP status codes.
+ if (response.Status != HttpStatusCode.OK && response.Status != HttpStatusCode.BadRequest) {
+ if (Logger.Channel.IsErrorEnabled) {
+ using (var reader = new StreamReader(response.ResponseStream)) {
+ Logger.Channel.ErrorFormat(
+ "Unexpected HTTP status code {0} {1} received in direct response:{2}{3}",
+ (int)response.Status,
+ response.Status,
+ Environment.NewLine,
+ reader.ReadToEnd());
+ }
+ }
+
+ // Call dispose before throwing since we're not including the response in the
+ // exception we're throwing.
+ response.Dispose();
+
+ ErrorUtilities.ThrowProtocol(OpenIdStrings.UnexpectedHttpStatusCode, (int)response.Status, response.Status);
+ }
+
+ return response;
+ }
+
+ /// <summary>
+ /// Initializes the binding elements.
+ /// </summary>
+ /// <param name="cryptoKeyStore">The 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 <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param>
+ /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param>
+ /// <returns>
+ /// An array of binding elements which may be used to construct the channel.
+ /// </returns>
+ private static IChannelBindingElement[] InitializeBindingElements(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings, bool nonVerifying) {
+ Contract.Requires<ArgumentNullException>(securitySettings != null);
+
+ SigningBindingElement signingElement;
+ signingElement = nonVerifying ? null : new SigningBindingElement(new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore ?? new MemoryCryptoKeyStore()));
+
+ var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration();
+
+ List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8);
+ elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings));
+ elements.Add(new RelyingPartySecurityOptions(securitySettings));
+ elements.Add(new BackwardCompatibilityBindingElement());
+ ReturnToNonceBindingElement requestNonceElement = null;
+
+ if (cryptoKeyStore != null) {
+ if (nonceStore != null) {
+ // There is no point in having a ReturnToNonceBindingElement without
+ // a ReturnToSignatureBindingElement because the nonce could be
+ // artificially changed without it.
+ requestNonceElement = new ReturnToNonceBindingElement(nonceStore, securitySettings);
+ elements.Add(requestNonceElement);
+ }
+
+ // It is important that the return_to signing element comes last
+ // so that the nonce is included in the signature.
+ elements.Add(new ReturnToSignatureBindingElement(cryptoKeyStore));
+ }
+
+ ErrorUtilities.VerifyOperation(!securitySettings.RejectUnsolicitedAssertions || requestNonceElement != null, OpenIdStrings.UnsolicitedAssertionRejectionRequiresNonceStore);
+
+ if (nonVerifying) {
+ elements.Add(new SkipSecurityBindingElement());
+ } else {
+ if (nonceStore != null) {
+ elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true));
+ }
+
+ elements.Add(new StandardExpirationBindingElement());
+ elements.Add(signingElement);
+ }
+
+ return elements.ToArray();
+ }
+
+ /// <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 <see cref="RelyingPartySecuritySettings"/> or <see cref="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) {
+ Contract.Requires<ArgumentNullException>(cryptoKeyStore != null);
+ Contract.Requires<ArgumentNullException>(securitySettings != null);
+ Contract.Requires<ArgumentNullException>(nonceStore != null);
+
+ SigningBindingElement signingElement;
+ signingElement = new SigningBindingElement(cryptoKeyStore, securitySettings);
+
+ var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration();
+
+ List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8);
+ elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings));
+ elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true));
+ elements.Add(new StandardExpirationBindingElement());
+ elements.Add(signingElement);
+
+ return elements.ToArray();
+ }
+ }
+}