summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-09-17 09:31:14 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2012-09-17 09:31:14 -0700
commita657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab (patch)
tree0d6d65a309ea68ae579789a3c3edf9401ca5fd8b /src
parent8c4b9e212fb967f8cbb83cc8e6fe1960b8663bb5 (diff)
parent4e616f9b5b125bde2bf4e936cd5b0030ddfa94f1 (diff)
downloadDotNetOpenAuth-a657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab.zip
DotNetOpenAuth-a657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab.tar.gz
DotNetOpenAuth-a657a72b7f6fb89d1a8adb82d2888a8d3f7f9bab.tar.bz2
Merge branch 'SlowGoogleServerWorkaround' into v4.1
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd23
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/HostMetaDiscoveryService.cs70
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs37
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs14
-rw-r--r--src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj1
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 &lt;hostMetaDiscovery&gt; 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" />