From 549ca8bd652d872f3a769514d2ffb4dd003199fe Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 25 Dec 2008 21:09:28 -0800 Subject: [PATCH] Added the rest of the RSA-SHA1 binding element's ability to handle X.509 certificates so that it can actually do signing and verification. --- src/DotNetOpenAuth/DotNetOpenAuth.csproj | 373 +++++++++++---------- .../IConsumerCertificateProvider.cs | 23 ++ .../RsaSha1SigningBindingElement.cs | 159 ++++++--- .../ChannelElements/SigningBindingElementBase.cs | 17 +- src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs | 288 ++++++++-------- src/DotNetOpenAuth/OAuth/OAuthStrings.resx | 292 ++++++++-------- 6 files changed, 637 insertions(+), 515 deletions(-) create mode 100644 src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 1df143c..b998273 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -1,186 +1,187 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7} - Library - Properties - DotNetOpenAuth - DotNetOpenAuth - v3.5 - 512 - - - true - full - false - ..\..\bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - ..\..\bin\Debug\DotNetOpenAuth.xml - false - -Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055 - - - pdbonly - true - ..\..\bin\Release\ - TRACE - prompt - 4 - false - ..\..\bin\debug\DotNetOpenAuth.xml - true - -Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055 - - - true - ..\official-build-key.pfx - $(DefineConstants);StrongNameSigned - - - - False - ..\..\lib\log4net.dll - - - - 3.5 - - - - 3.0 - - - - - 3.5 - - - - - - - - - - - - - - - - - - - - - - - - - True - True - OAuthStrings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - MessagingStrings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - ResXFileCodeGenerator - MessagingStrings.Designer.cs - - - ResXFileCodeGenerator - OAuthStrings.Designer.cs - - - ResXFileCodeGenerator - Strings.Designer.cs - Designer - - - - - + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {3191B653-F76D-4C1A-9A5A-347BC3AAAAB7} + Library + Properties + DotNetOpenAuth + DotNetOpenAuth + v3.5 + 512 + + + true + full + false + ..\..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + ..\..\bin\Debug\DotNetOpenAuth.xml + false + -Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055 + + + pdbonly + true + ..\..\bin\Release\ + TRACE + prompt + 4 + false + ..\..\bin\debug\DotNetOpenAuth.xml + true + -Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055 + + + true + ..\official-build-key.pfx + $(DefineConstants);StrongNameSigned + + + + False + ..\..\lib\log4net.dll + + + + 3.5 + + + + 3.0 + + + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + OAuthStrings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + MessagingStrings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + ResXFileCodeGenerator + MessagingStrings.Designer.cs + + + ResXFileCodeGenerator + OAuthStrings.Designer.cs + + + ResXFileCodeGenerator + Strings.Designer.cs + Designer + + + + + \ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs new file mode 100644 index 0000000..22c8542 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Andrew Arnott. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System.Security.Cryptography.X509Certificates; + + /// + /// A provider that hosts can implement to hook up their RSA-SHA1 binding elements + /// to their list of known Consumers' certificates. + /// + public interface IConsumerCertificateProvider { + /// + /// Gets the certificate that can be used to verify the signature of an incoming + /// message from a Consumer. + /// + /// The incoming message from some Consumer. + /// The public key from the Consumer's X.509 Certificate, if one can be found; otherwise null. + X509Certificate2 GetCertificate(ITamperResistantOAuthMessage consumerMessage); + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs index 058ce39..576ec8c 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs @@ -1,48 +1,111 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Andrew Arnott. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Messaging; - - /// - /// A binding element that signs outgoing messages and verifies the signature on incoming messages. - /// - public class RsaSha1SigningBindingElement : SigningBindingElementBase { - /// - /// Initializes a new instance of the class. - /// - internal RsaSha1SigningBindingElement() - : base("RSA-SHA1") { - } - - /// - /// Calculates a signature for a given message. - /// - /// The message to sign. - /// The signature for the message. - /// - /// This method signs the message per OAuth 1.0 section 9.3. - /// - protected override string GetSignature(ITamperResistantOAuthMessage message) { - AsymmetricAlgorithm provider = new RSACryptoServiceProvider(); - AsymmetricSignatureFormatter hasher = new RSAPKCS1SignatureFormatter(provider); - hasher.SetHashAlgorithm("SHA1"); - byte[] digest = hasher.CreateSignature(Encoding.ASCII.GetBytes(ConstructSignatureBaseString(message))); - return Convert.ToBase64String(digest); - } - - /// - /// Clones this instance. - /// - /// A new instance of the binding element. - protected override ITamperProtectionChannelBindingElement Clone() { - return new RsaSha1SigningBindingElement(); - } - } -} +//----------------------------------------------------------------------- +// +// Copyright (c) Andrew Arnott. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// + /// A binding element that signs outgoing messages and verifies the signature on incoming messages. + /// + public class RsaSha1SigningBindingElement : SigningBindingElementBase { + /// + /// Initializes a new instance of the class + /// for use by Consumers. + /// + /// The certificate used to sign outgoing messages. + internal RsaSha1SigningBindingElement(X509Certificate2 signingCertificate) + : this() { + if (signingCertificate == null) { + throw new ArgumentNullException("signingCertificate"); + } + + this.SigningCertificate = signingCertificate; + } + + /// + /// Initializes a new instance of the class + /// for use by Service Providers. + /// + internal RsaSha1SigningBindingElement() + : base("RSA-SHA1") { + } + + /// + /// Gets or sets the certificate used to sign outgoing messages. + /// + public X509Certificate2 SigningCertificate { get; set; } + + /// + /// Gets or sets the consumer certificate provider. + /// + public IConsumerCertificateProvider ConsumerCertificateProvider { get; set; } + + /// + /// Calculates a signature for a given message. + /// + /// The message to sign. + /// The signature for the message. + /// + /// This method signs the message per OAuth 1.0 section 9.3. + /// + protected override string GetSignature(ITamperResistantOAuthMessage message) { + if (message == null) { + throw new ArgumentNullException("message"); + } + + if (this.SigningCertificate == null) { + throw new InvalidOperationException(OAuthStrings.X509CertificateNotProvidedForSigning); + } + + string signatureBaseString = ConstructSignatureBaseString(message); + byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); + var provider = (RSACryptoServiceProvider)this.SigningCertificate.PublicKey.Key; + byte[] binarySignature = provider.SignData(data, "SHA1"); + string base64Signature = Convert.ToBase64String(binarySignature); + return base64Signature; + } + + /// + /// Determines whether the signature on some message is valid. + /// + /// The message to check the signature on. + /// + /// true if the signature on the message is valid; otherwise, false. + /// + protected override bool IsSignatureValid(ITamperResistantOAuthMessage message) { + if (this.ConsumerCertificateProvider == null) { + throw new InvalidOperationException(OAuthStrings.ConsumerCertificateProviderNotAvailable); + } + + string signatureBaseString = ConstructSignatureBaseString(message); + byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); + + byte[] carriedSignature = Convert.FromBase64String(message.Signature); + + X509Certificate2 cert = this.ConsumerCertificateProvider.GetCertificate(message); + if (cert == null) { + Logger.WarnFormat("Incoming message from consumer '{0}' could not be matched with an appropriate X.509 certificate for signature verification.", message.ConsumerKey); + return false; + } + + var provider = (RSACryptoServiceProvider)cert.PublicKey.Key; + bool valid = provider.VerifyData(data, "SHA1", carriedSignature); + return valid; + } + + /// + /// Clones this instance. + /// + /// A new instance of the binding element. + protected override ITamperProtectionChannelBindingElement Clone() { + return new RsaSha1SigningBindingElement(); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs index 704a362..b1f6de0 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -106,11 +106,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { if (this.SignatureCallback != null) { this.SignatureCallback(signedMessage); } else { - Logger.Warn("Signature verification required, but callback delegate was not provided to provide additional data for signing."); + Logger.Warn("Signature verification required, but callback delegate was not provided to provide additional data for signature verification."); } - string signature = this.GetSignature(signedMessage); - if (signedMessage.Signature != signature) { + if (!this.IsSignatureValid(signedMessage)) { Logger.Error("Signature verification failed."); throw new InvalidSignatureException(message); } @@ -196,6 +195,18 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { builder.Append(Uri.EscapeDataString(message.TokenSecret)); } return builder.ToString(); + } + + /// + /// Determines whether the signature on some message is valid. + /// + /// The message to check the signature on. + /// + /// true if the signature on the message is valid; otherwise, false. + /// + protected virtual bool IsSignatureValid(ITamperResistantOAuthMessage message) { + string signature = this.GetSignature(message); + return message.Signature == signature; } /// diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs index 8d42b10..dcf6e8b 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs @@ -1,135 +1,153 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:2.0.50727.3053 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace DotNetOpenAuth.OAuth { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class OAuthStrings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal OAuthStrings() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OAuth.OAuthStrings", typeof(OAuthStrings).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Cannot send access token to Consumer for request token '{0}' before it has been authorized.. - /// - internal static string AccessTokenNotAuthorized { - get { - return ResourceManager.GetString("AccessTokenNotAuthorized", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The access token '{0}' is invalid or expired.. - /// - internal static string BadAccessTokenInProtectedResourceRequest { - get { - return ResourceManager.GetString("BadAccessTokenInProtectedResourceRequest", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failure looking up secret for consumer or token.. - /// - internal static string ConsumerOrTokenSecretNotFound { - get { - return ResourceManager.GetString("ConsumerOrTokenSecretNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An invalid OAuth message received and discarded.. - /// - internal static string InvalidIncomingMessage { - get { - return ResourceManager.GetString("InvalidIncomingMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} message included extra data which is not allowed.. - /// - internal static string MessageNotAllowedExtraParameters { - get { - return ResourceManager.GetString("MessageNotAllowedExtraParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The request URL query MUST NOT contain any OAuth Protocol Parameters.. - /// - internal static string RequestUrlMustNotHaveOAuthParameters { - get { - return ResourceManager.GetString("RequestUrlMustNotHaveOAuthParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The signing element already has been associated with a channel.. - /// - internal static string SigningElementAlreadyAssociatedWithChannel { - get { - return ResourceManager.GetString("SigningElementAlreadyAssociatedWithChannel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to All signing elements must offer the same message protection.. - /// - internal static string SigningElementsMustShareSameProtection { - get { - return ResourceManager.GetString("SigningElementsMustShareSameProtection", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3521 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OAuth { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class OAuthStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal OAuthStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OAuth.OAuthStrings", typeof(OAuthStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Cannot send access token to Consumer for request token '{0}' before it has been authorized.. + /// + internal static string AccessTokenNotAuthorized { + get { + return ResourceManager.GetString("AccessTokenNotAuthorized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The access token '{0}' is invalid or expired.. + /// + internal static string BadAccessTokenInProtectedResourceRequest { + get { + return ResourceManager.GetString("BadAccessTokenInProtectedResourceRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The RSA-SHA1 signing binding element's consumer certificate provider has not been set, so no incoming messages from consumers using this signature method can be verified.. + /// + internal static string ConsumerCertificateProviderNotAvailable { + get { + return ResourceManager.GetString("ConsumerCertificateProviderNotAvailable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failure looking up secret for consumer or token.. + /// + internal static string ConsumerOrTokenSecretNotFound { + get { + return ResourceManager.GetString("ConsumerOrTokenSecretNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An invalid OAuth message received and discarded.. + /// + internal static string InvalidIncomingMessage { + get { + return ResourceManager.GetString("InvalidIncomingMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} message included extra data which is not allowed.. + /// + internal static string MessageNotAllowedExtraParameters { + get { + return ResourceManager.GetString("MessageNotAllowedExtraParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request URL query MUST NOT contain any OAuth Protocol Parameters.. + /// + internal static string RequestUrlMustNotHaveOAuthParameters { + get { + return ResourceManager.GetString("RequestUrlMustNotHaveOAuthParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The signing element already has been associated with a channel.. + /// + internal static string SigningElementAlreadyAssociatedWithChannel { + get { + return ResourceManager.GetString("SigningElementAlreadyAssociatedWithChannel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All signing elements must offer the same message protection.. + /// + internal static string SigningElementsMustShareSameProtection { + get { + return ResourceManager.GetString("SigningElementsMustShareSameProtection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The RSA-SHA1 signing binding element has not been set with a certificate for signing.. + /// + internal static string X509CertificateNotProvidedForSigning { + get { + return ResourceManager.GetString("X509CertificateNotProvidedForSigning", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx index 3e59ca9..5ba71c7 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx @@ -1,144 +1,150 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Cannot send access token to Consumer for request token '{0}' before it has been authorized. - - - The access token '{0}' is invalid or expired. - - - Failure looking up secret for consumer or token. - - - An invalid OAuth message received and discarded. - - - The {0} message included extra data which is not allowed. - - - The request URL query MUST NOT contain any OAuth Protocol Parameters. - - - The signing element already has been associated with a channel. - - - All signing elements must offer the same message protection. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cannot send access token to Consumer for request token '{0}' before it has been authorized. + + + The access token '{0}' is invalid or expired. + + + The RSA-SHA1 signing binding element's consumer certificate provider has not been set, so no incoming messages from consumers using this signature method can be verified. + + + Failure looking up secret for consumer or token. + + + An invalid OAuth message received and discarded. + + + The {0} message included extra data which is not allowed. + + + The request URL query MUST NOT contain any OAuth Protocol Parameters. + + + The signing element already has been associated with a channel. + + + All signing elements must offer the same message protection. + + + The RSA-SHA1 signing binding element has not been set with a certificate for signing. + \ No newline at end of file -- 2.11.4.GIT