diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2012-09-17 09:31:14 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2012-09-17 09:31:14 -0700 |
commit | a657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab (patch) | |
tree | 0d6d65a309ea68ae579789a3c3edf9401ca5fd8b /src | |
parent | 8c4b9e212fb967f8cbb83cc8e6fe1960b8663bb5 (diff) | |
parent | 4e616f9b5b125bde2bf4e936cd5b0030ddfa94f1 (diff) | |
download | DotNetOpenAuth-a657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab.zip DotNetOpenAuth-a657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab.tar.gz DotNetOpenAuth-a657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab.tar.bz2 |
Merge branch 'SlowGoogleServerWorkaround' into v4.1
Diffstat (limited to 'src')
5 files changed, 128 insertions, 17 deletions
diff --git a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd index 74d4db4..8a970f4 100644 --- a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd +++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd @@ -475,6 +475,11 @@ </xs:complexType> </xs:element> <xs:element name="discoveryServices"> + <xs:annotation> + <xs:documentation> + Adds or removes OpenID discovery mechanisms to use on OpenID identifiers. + </xs:documentation> + </xs:annotation> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="add"> @@ -502,6 +507,24 @@ </xs:choice> </xs:complexType> </xs:element> + <xs:element name="hostMetaDiscovery"> + <xs:annotation> + <xs:documentation> + Customizes the non-standard host-meta discovery process, when that discovery service is enabled. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="enableCertificateValidationCache" type="xs:boolean" default="false"> + <xs:annotation> + <xs:documentation> + Allows DotNetOpenAuth to remember X509Certificates that it has already verified are valid + to avoid validating them each time. Use when operating on a server with long delays when + validating certificates. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> <xs:element name="store"> <xs:annotation> <xs:documentation> diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs index 450f9e0..9e81c48 100644 --- a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs +++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; @@ -21,6 +22,7 @@ namespace DotNetOpenAuth.OpenId { using System.Text.RegularExpressions; using System.Xml; using System.Xml.XPath; + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.Xrds; @@ -53,6 +55,11 @@ namespace DotNetOpenAuth.OpenId { private static readonly Regex HostMetaLink = new Regex(@"^Link: <(?<location>.+?)>; rel=""describedby http://reltype.google.com/openid/xrd-op""; type=""application/xrds\+xml""$"); /// <summary> + /// A set of certificate thumbprints that have been verified. + /// </summary> + private static readonly HashSet<string> ApprovedCertificateThumbprintCache = new HashSet<string>(StringComparer.Ordinal); + + /// <summary> /// Initializes a new instance of the <see cref="HostMetaDiscoveryService"/> class. /// </summary> public HostMetaDiscoveryService() { @@ -232,21 +239,7 @@ namespace DotNetOpenAuth.OpenId { ErrorUtilities.VerifyProtocol(certNodes.Count > 0, OpenIdStrings.MissingElement, "X509Certificate"); var certs = certNodes.Cast<XPathNavigator>().Select(n => new X509Certificate2(Convert.FromBase64String(n.Value.Trim()))).ToList(); - // Verify that we trust the signer of the certificates. - // Start by trying to validate just the certificate used to sign the XRDS document, - // since we can do that with partial trust. - Logger.OpenId.Debug("Verifying that we trust the certificate used to sign the discovery document."); - if (!certs[0].Verify()) { - // We couldn't verify just the signing certificate, so try to verify the whole certificate chain. - try { - Logger.OpenId.Debug("Verifying the whole certificate chain."); - VerifyCertChain(certs); - Logger.OpenId.Debug("Certificate chain verified."); - } catch (SecurityException) { - Logger.Yadis.Warn("Signing certificate verification failed and we have insufficient code access security permissions to perform certificate chain validation."); - ErrorUtilities.ThrowProtocol(OpenIdStrings.X509CertificateNotTrusted); - } - } + VerifyCertificateChain(certs); // Verify that the certificate is issued to the host on whom we are performing discovery. string hostName = certs[0].GetNameInfo(X509NameType.DnsName, false); @@ -272,8 +265,9 @@ namespace DotNetOpenAuth.OpenId { /// an alternative plan. /// </remarks> /// <exception cref="ProtocolException">Thrown if the certificate chain is invalid or unverifiable.</exception> - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.ThrowProtocol(System.String,System.Object[])", Justification = "The localized portion is a string resource already."), SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "By design")] - private static void VerifyCertChain(List<X509Certificate2> certs) { + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.ThrowProtocol(System.String,System.Object[])", Justification = "The localized portion is a string resource already.")] + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "By design")] + private static void VerifyCertChain(IEnumerable<X509Certificate2> certs) { var chain = new X509Chain(); foreach (var cert in certs) { chain.Build(cert); @@ -317,6 +311,48 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Verifies that a certificate chain is trusted. + /// </summary> + /// <param name="certificates">The chain of certificates to verify.</param> + private static void VerifyCertificateChain(IList<X509Certificate2> certificates) { + Contract.Requires(certificates.Count > 0); + Contract.Requires(certificates.All(c => c != null)); + + // Before calling into the OS to validate the certificate, since that can for some bizzare reason hang for 5 seconds + // on some systems, check a cache of previously verified certificates first. + if (OpenIdElement.Configuration.RelyingParty.HostMetaDiscovery.EnableCertificateValidationCache) { + lock (ApprovedCertificateThumbprintCache) { + // HashSet<T> isn't thread-safe. + if (ApprovedCertificateThumbprintCache.Contains(certificates[0].Thumbprint)) { + return; + } + } + } + + // Verify that we trust the signer of the certificates. + // Start by trying to validate just the certificate used to sign the XRDS document, + // since we can do that with partial trust. + Logger.OpenId.Debug("Verifying that we trust the certificate used to sign the discovery document."); + if (!certificates[0].Verify()) { + // We couldn't verify just the signing certificate, so try to verify the whole certificate chain. + try { + Logger.OpenId.Debug("Verifying the whole certificate chain."); + VerifyCertChain(certificates); + Logger.OpenId.Debug("Certificate chain verified."); + } catch (SecurityException) { + Logger.Yadis.Warn("Signing certificate verification failed and we have insufficient code access security permissions to perform certificate chain validation."); + ErrorUtilities.ThrowProtocol(OpenIdStrings.X509CertificateNotTrusted); + } + } + + if (OpenIdElement.Configuration.RelyingParty.HostMetaDiscovery.EnableCertificateValidationCache) { + lock (ApprovedCertificateThumbprintCache) { + ApprovedCertificateThumbprintCache.Add(certificates[0].Thumbprint); + } + } + } + + /// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> diff --git a/src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs new file mode 100644 index 0000000..437b12f --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// <copyright file="HostMetaDiscoveryElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + + /// <summary> + /// The configuration element that can adjust how hostmeta discovery works. + /// </summary> + internal class HostMetaDiscoveryElement : ConfigurationElement { + /// <summary> + /// The property name for enableCertificateValidationCache. + /// </summary> + private const string EnableCertificateValidationCacheConfigName = "enableCertificateValidationCache"; + + /// <summary> + /// Initializes a new instance of the <see cref="HostMetaDiscoveryElement"/> class. + /// </summary> + public HostMetaDiscoveryElement() { + } + + /// <summary> + /// Gets or sets a value indicating whether validated certificates should be cached and not validated again. + /// </summary> + /// <remarks> + /// This helps to avoid unexplained 5-10 second delays in certificate validation for Google Apps for Domains that impact some servers. + /// </remarks> + [ConfigurationProperty(EnableCertificateValidationCacheConfigName, DefaultValue = false)] + public bool EnableCertificateValidationCache { + get { return (bool)this[EnableCertificateValidationCacheConfigName]; } + set { this[EnableCertificateValidationCacheConfigName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs index 7d8c050..8af1129 100644 --- a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs +++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs @@ -47,6 +47,11 @@ namespace DotNetOpenAuth.Configuration { private const string DiscoveryServicesElementName = "discoveryServices"; /// <summary> + /// The name of the <hostMetaDiscovery> sub-element. + /// </summary> + private const string HostMetaDiscoveryElementName = "hostMetaDiscovery"; + + /// <summary> /// The built-in set of identifier discovery services. /// </summary> private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices = @@ -99,6 +104,15 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the host meta discovery configuration element. + /// </summary> + [ConfigurationProperty(HostMetaDiscoveryElementName)] + internal HostMetaDiscoveryElement HostMetaDiscovery { + get { return (HostMetaDiscoveryElement)this[HostMetaDiscoveryElementName] ?? new HostMetaDiscoveryElement(); } + set { this[HostMetaDiscoveryElementName] = value; } + } + + /// <summary> /// Gets or sets the services to use for discovering service endpoints for identifiers. /// </summary> /// <remarks> diff --git a/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj b/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj index 95dccc1..75bd113 100644 --- a/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj +++ b/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj @@ -22,6 +22,7 @@ <ItemGroup> <Compile Include="Configuration\AssociationTypeCollection.cs" /> <Compile Include="Configuration\AssociationTypeElement.cs" /> + <Compile Include="Configuration\HostMetaDiscoveryElement.cs" /> <Compile Include="Configuration\OpenIdElement.cs" /> <Compile Include="Configuration\OpenIdProviderElement.cs" /> <Compile Include="Configuration\OpenIdProviderSecuritySettingsElement.cs" /> |