public void CtorGoodUri() { var uri = new UriIdentifier(this.goodUri); Assert.AreEqual(new Uri(this.goodUri), uri.Uri); Assert.IsFalse(uri.SchemeImplicitlyPrepended); Assert.IsFalse(uri.IsDiscoverySecureEndToEnd); }
public void DiscoveryRequireSslWithInsecureXrdsInSecureHtmlHead() { var insecureXrdsSource = this.GetMockIdentifier(ProtocolVersion.V20, false); Uri secureClaimedUri = new Uri("https://localhost/secureId"); string html = string.Format("<html><head><meta http-equiv='X-XRDS-Location' content='{0}'/></head><body></body></html>", insecureXrdsSource); this.MockResponder.RegisterMockResponse(secureClaimedUri, "text/html", html); Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); Assert.AreEqual(0, this.Discover(userSuppliedIdentifier).Count()); }
internal static void RegisterMockXrdsResponse(this TestBase test, UriIdentifier directedIdentityAssignedIdentifier, IdentifierDiscoveryResult providerEndpoint) { IdentifierDiscoveryResult identityEndpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier( directedIdentityAssignedIdentifier, directedIdentityAssignedIdentifier, providerEndpoint.ProviderLocalIdentifier, new ProviderEndpointDescription(providerEndpoint.ProviderEndpoint, providerEndpoint.Capabilities), 10, 10); RegisterMockXrdsResponse(test, identityEndpoint); }
/// <summary> /// Tests equality between this URI and another URI. /// </summary> /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> /// <returns> /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. /// </returns> /// <exception cref="T:System.NullReferenceException"> /// The <paramref name="obj"/> parameter is null. /// </exception> public override bool Equals(object obj) { UriIdentifier other = obj as UriIdentifier; if (other == null) { return(false); } return(this.Uri == other.Uri); }
private static void ValidateXmlDSig(XrdsDocument document, UriIdentifier identifier, IncomingWebResponse response, string signingHost) { Requires.NotNull(document, "document"); Requires.NotNull(identifier, "identifier"); Requires.NotNull(response, "response"); var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver); ErrorUtilities.VerifyProtocol(signatureNode != null, OpenIdStrings.MissingElement, "Signature"); var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver); ErrorUtilities.VerifyProtocol(signedInfoNode != null, OpenIdStrings.MissingElement, "SignedInfo"); ErrorUtilities.VerifyProtocol( signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null, OpenIdStrings.UnsupportedCanonicalizationMethod); ErrorUtilities.VerifyProtocol( signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null, OpenIdStrings.UnsupportedSignatureMethod); var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver); 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); } } // Verify that the certificate is issued to the host on whom we are performing discovery. string hostName = certs[0].GetNameInfo(X509NameType.DnsName, false); ErrorUtilities.VerifyProtocol(string.Equals(hostName, signingHost, StringComparison.OrdinalIgnoreCase), OpenIdStrings.MisdirectedSigningCertificate, hostName, signingHost); // Verify the signature itself byte[] signature = Convert.FromBase64String(response.Headers["Signature"]); var provider = (RSACryptoServiceProvider)certs.First().PublicKey.Key; byte[] data = new byte[response.ResponseStream.Length]; response.ResponseStream.Seek(0, SeekOrigin.Begin); response.ResponseStream.Read(data, 0, data.Length); ErrorUtilities.VerifyProtocol(provider.VerifyData(data, "SHA1", signature), OpenIdStrings.InvalidDSig); }
public void DiscoveryWithRedirects() { Identifier claimedId = this.GetMockIdentifier(ProtocolVersion.V20, false); // Add a couple of chained redirect pages that lead to the claimedId. Uri userSuppliedUri = new Uri("https://localhost/someSecurePage"); Uri insecureMidpointUri = new Uri("http://localhost/insecureStop"); this.MockResponder.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); this.MockResponder.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); // don't require secure SSL discovery for this test. Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, false); Assert.AreEqual(1, this.Discover(userSuppliedIdentifier).Count()); }
public void DiscoverRequireSslWithSecureRedirects() { Identifier claimedId = this.GetMockIdentifier(ProtocolVersion.V20, true); // Add a couple of chained redirect pages that lead to the claimedId. // All redirects should be secure. Uri userSuppliedUri = new Uri("https://localhost/someSecurePage"); Uri secureMidpointUri = new Uri("https://localhost/secureStop"); this.MockResponder.RegisterMockRedirect(userSuppliedUri, secureMidpointUri); this.MockResponder.RegisterMockRedirect(secureMidpointUri, new Uri(claimedId.ToString())); Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); Assert.AreEqual(1, this.Discover(userSuppliedIdentifier).Count()); }
/// <summary> /// Creates the service endpoints described in this document, useful for requesting /// authentication of one of the OpenID Providers that result from it. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <returns> /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. /// </returns> internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { Requires.NotNull(xrds, "xrds"); Requires.NotNull(claimedIdentifier, "claimedIdentifier"); Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); var endpoints = new List<IdentifierDiscoveryResult>(); endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); Logger.Yadis.Debug(endpoints.ToStringDeferred(true)); return endpoints; }
/// <summary> /// Performs YADIS discovery on some identifier. /// </summary> /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> /// <param name="uri">The URI to perform discovery on.</param> /// <param name="requireSsl">Whether discovery should fail if any step of it is not encrypted.</param> /// <returns> /// The result of discovery on the given URL. /// Null may be returned if an error occurs, /// or if <paramref name="requireSsl"/> is true but part of discovery /// is not protected by SSL. /// </returns> public static DiscoveryResult Discover(IDirectWebRequestHandler requestHandler, UriIdentifier uri, bool requireSsl) { CachedDirectWebResponse response; try { if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { Logger.Yadis.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri); return null; } response = Request(requestHandler, uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan); if (response.Status != System.Net.HttpStatusCode.OK) { Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response.Status, response.Status, uri); return null; } } catch (ArgumentException ex) { // Unsafe URLs generate this Logger.Yadis.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri, ex); return null; } CachedDirectWebResponse response2 = null; if (IsXrdsDocument(response)) { Logger.Yadis.Debug("An XRDS response was received from GET at user-supplied identifier."); response2 = response; } else { string uriString = response.Headers.Get(HeaderName); Uri url = null; if (uriString != null) { if (Uri.TryCreate(uriString, UriKind.Absolute, out url)) { Logger.Yadis.DebugFormat("{0} found in HTTP header. Preparing to pull XRDS from {1}", HeaderName, url); } } if (url == null && response.ContentType != null && response.ContentType.MediaType == ContentTypes.Html) { url = FindYadisDocumentLocationInHtmlMetaTags(response.GetResponseString()); if (url != null) { Logger.Yadis.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName, url); } } if (url != null) { if (!requireSsl || string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { response2 = Request(requestHandler, url, requireSsl, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan); if (response2.Status != HttpStatusCode.OK) { Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response2.Status, response2.Status, uri); return null; } } else { Logger.Yadis.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url); } } } return new DiscoveryResult(uri, response, response2); }
/// <summary> /// Creates the service endpoints described in this document, useful for requesting /// authentication of one of the OpenID Providers that result from it. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <returns> /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. /// </returns> internal static IEnumerable<ServiceEndpoint> CreateServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { var endpoints = new List<ServiceEndpoint>(); endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); // If any OP Identifier service elements were found, we must not proceed // to return any Claimed Identifier services. if (endpoints.Count == 0) { endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); } Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); Logger.Yadis.Debug(endpoints.ToStringDeferred(true)); return endpoints; }
public void DiscoverRequireSslWithInsecureRedirect() { Identifier claimedId = this.GetMockIdentifier(ProtocolVersion.V20, true); // Add a couple of chained redirect pages that lead to the claimedId. // Include an insecure HTTP jump in those redirects to verify that // the ultimate endpoint is never found as a result of high security profile. Uri userSuppliedUri = new Uri("https://localhost/someSecurePage"); Uri insecureMidpointUri = new Uri("http://localhost/insecureStop"); this.MockResponder.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); this.MockResponder.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); this.Discover(userSuppliedIdentifier); }
/// <summary> /// Tests equality between this URI and another URI. /// </summary> /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> /// <returns> /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. /// </returns> /// <exception cref="T:System.NullReferenceException"> /// The <paramref name="obj"/> parameter is null. /// </exception> public override bool Equals(object obj) { UriIdentifier other = obj as UriIdentifier; if (obj != null && other == null && Identifier.EqualityOnStrings) // test hook to enable MockIdentifier comparison { other = Identifier.Parse(obj.ToString()) as UriIdentifier; } if (other == null) { return(false); } return(this.Uri == other.Uri); }
/// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> /// <returns>A HTTP response carrying an XRDS document, or <c>null</c> if one could not be obtained.</returns> /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> private IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { Requires.NotNull(identifier, "identifier"); Requires.NotNull(requestHandler, "requestHandler"); Uri xrdsLocation = this.GetXrdsLocation(identifier, requestHandler, out signingHost); if (xrdsLocation == null) { return(null); } var response = GetXrdsResponse(identifier, requestHandler, xrdsLocation); return(response); }
/// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> /// <returns>A HTTP response carrying an XRDS document, or <c>null</c> if one could not be obtained.</returns> /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> private IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { Contract.Requires <ArgumentNullException>(identifier != null); Contract.Requires <ArgumentNullException>(requestHandler != null); Uri xrdsLocation = this.GetXrdsLocation(identifier, requestHandler, out signingHost); if (xrdsLocation == null) { return(null); } var response = GetXrdsResponse(identifier, requestHandler, xrdsLocation); return(response); }
public static Identifier Parse(string identifier, bool serializeExactValue) { Requires.NotNullOrEmpty(identifier, "identifier"); Identifier id; if (XriIdentifier.IsValidXri(identifier)) { id = new XriIdentifier(identifier); } else { id = new UriIdentifier(identifier); } id.IsDeserializedInstance = serializeExactValue; return(id); }
/// <summary> /// Gets the URIs authorized to host host-meta documents on behalf of a given domain. /// </summary> /// <param name="identifier">The identifier.</param> /// <returns>A sequence of URIs that MAY provide the host-meta for a given identifier.</returns> private IEnumerable <HostMetaProxy> GetHostMetaLocations(UriIdentifier identifier) { Contract.Requires <ArgumentNullException>(identifier != null); // First try the proxies, as they are considered more "secure" than the local // host-meta for a domain since the domain may be defaced. IEnumerable <HostMetaProxy> result = this.TrustedHostMetaProxies; // Finally, look for the local host-meta. UriBuilder localHostMetaBuilder = new UriBuilder(); localHostMetaBuilder.Scheme = identifier.IsDiscoverySecureEndToEnd || identifier.Uri.IsTransportSecure() ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; localHostMetaBuilder.Host = identifier.Uri.Host; localHostMetaBuilder.Path = LocalHostMetaPath; result = result.Concat(new[] { new HostMetaProxy(localHostMetaBuilder.Uri.AbsoluteUri, identifier.Uri.Host) }); return(result); }
public static Identifier Parse(string identifier, bool serializeExactValue) { Contract.Requires <ArgumentException>(!String.IsNullOrEmpty(identifier)); Contract.Ensures(Contract.Result <Identifier>() != null); Identifier id; if (XriIdentifier.IsValidXri(identifier)) { id = new XriIdentifier(identifier); } else { id = new UriIdentifier(identifier); } id.IsDeserializedInstance = serializeExactValue; return(id); }
/// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="xrdsLocation">The location of the XRDS document to retrieve.</param> /// <returns> /// A HTTP response carrying an XRDS document. /// </returns> /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> private static IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, Uri xrdsLocation) { Requires.NotNull(identifier, "identifier"); Requires.NotNull(requestHandler, "requestHandler"); Requires.NotNull(xrdsLocation, "xrdsLocation"); var request = (HttpWebRequest)WebRequest.Create(xrdsLocation); request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; request.Accept = ContentTypes.Xrds; var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None; var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) { Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType); } return(response); }
private static async Task ValidateXmlDSigAsync(XrdsDocument document, UriIdentifier identifier, HttpResponseMessage response, string signingHost) { Requires.NotNull(document, "document"); Requires.NotNull(identifier, "identifier"); Requires.NotNull(response, "response"); var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver); ErrorUtilities.VerifyProtocol(signatureNode != null, OpenIdStrings.MissingElement, "Signature"); var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver); ErrorUtilities.VerifyProtocol(signedInfoNode != null, OpenIdStrings.MissingElement, "SignedInfo"); ErrorUtilities.VerifyProtocol( signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null, OpenIdStrings.UnsupportedCanonicalizationMethod); ErrorUtilities.VerifyProtocol( signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null, OpenIdStrings.UnsupportedSignatureMethod); var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver); ErrorUtilities.VerifyProtocol(certNodes.Count > 0, OpenIdStrings.MissingElement, "X509Certificate"); var certs = certNodes.Cast <XPathNavigator>().Select(n => new X509Certificate2(Convert.FromBase64String(n.Value.Trim()))).ToList(); 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); ErrorUtilities.VerifyProtocol(string.Equals(hostName, signingHost, StringComparison.OrdinalIgnoreCase), OpenIdStrings.MisdirectedSigningCertificate, hostName, signingHost); // Verify the signature itself byte[] signature = Convert.FromBase64String(response.Headers.GetValues("Signature").First()); var provider = (RSACryptoServiceProvider)certs.First().PublicKey.Key; var responseStream = await response.Content.ReadAsStreamAsync(); byte[] data = new byte[responseStream.Length]; responseStream.Seek(0, SeekOrigin.Begin); await responseStream.ReadAsync(data, 0, data.Length); ErrorUtilities.VerifyProtocol(provider.VerifyData(data, "SHA1", signature), OpenIdStrings.InvalidDSig); }
/// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="xrdsLocation">The location of the XRDS document to retrieve.</param> /// <returns> /// A HTTP response carrying an XRDS document. /// </returns> /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> private static IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, Uri xrdsLocation) { Contract.Requires <ArgumentNullException>(identifier != null); Contract.Requires <ArgumentNullException>(requestHandler != null); Contract.Requires <ArgumentNullException>(xrdsLocation != null); Contract.Ensures(Contract.Result <IncomingWebResponse>() != null); var request = (HttpWebRequest)WebRequest.Create(xrdsLocation); request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; request.Accept = ContentTypes.Xrds; var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None; var response = requestHandler.GetResponse(request, options); if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) { Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType); } return(response); }
/// <summary> /// Tests equality between this URI and another URI. /// </summary> /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> /// <returns> /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false. /// </returns> /// <exception cref="T:System.NullReferenceException"> /// The <paramref name="obj"/> parameter is null. /// </exception> public override bool Equals(object obj) { UriIdentifier other = obj as UriIdentifier; if (obj != null && other == null && Identifier.EqualityOnStrings) // test hook to enable MockIdentifier comparison { other = Identifier.Parse(obj.ToString()) as UriIdentifier; } if (other == null) { return(false); } if (this.ProblematicNormalization || other.ProblematicNormalization) { return(new SimpleUri(this.OriginalString).Equals(new SimpleUri(other.OriginalString))); } else { return(this.Uri == other.Uri); } }
/// <summary> /// Gets the host-meta for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> /// <returns> /// The host-meta response, or <c>null</c> if no host-meta document could be obtained. /// </returns> private IncomingWebResponse GetHostMeta(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { Requires.NotNull(identifier, "identifier"); Requires.NotNull(requestHandler, "requestHandler"); foreach (var hostMetaProxy in this.GetHostMetaLocations(identifier)) { var hostMetaLocation = hostMetaProxy.GetProxy(identifier); var request = (HttpWebRequest)WebRequest.Create(hostMetaLocation); request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; var options = DirectWebRequestOptions.AcceptAllHttpResponses; if (identifier.IsDiscoverySecureEndToEnd) { options |= DirectWebRequestOptions.RequireSsl; } var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); try { if (response.Status == HttpStatusCode.OK) { Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation); signingHost = hostMetaProxy.GetSigningHost(identifier); return(response); } else { Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation); response.Dispose(); } } catch { response.Dispose(); throw; } } signingHost = null; return(null); }
/// <summary> /// Creates the service endpoints described in this document, useful for requesting /// authentication of one of the OpenID Providers that result from it. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <returns> /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. /// </returns> internal static IEnumerable <ServiceEndpoint> CreateServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { var endpoints = new List <ServiceEndpoint>(); endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(claimedIdentifier)); // If any OP Identifier service elements were found, we must not proceed // to return any Claimed Identifier services. if (endpoints.Count == 0) { endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); } Logger.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); Logger.Debug(endpoints.ToStringDeferred(true)); return(endpoints); }
/// <summary> /// Creates the service endpoints described in this document, useful for requesting /// authentication of one of the OpenID Providers that result from it. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <returns> /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. /// </returns> internal static IEnumerable <IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable <XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { Requires.NotNull(xrds, "xrds"); Requires.NotNull(claimedIdentifier, "claimedIdentifier"); Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); var endpoints = new List <IdentifierDiscoveryResult>(); endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); Logger.Yadis.Debug(endpoints.ToStringDeferred(true).ToString()); return(endpoints); }
/// <summary> /// Gets the signing host URI. /// </summary> /// <param name="identifier">The identifier being discovered.</param> /// <returns>A host name.</returns> public virtual string GetSigningHost(UriIdentifier identifier) { Contract.Requires<ArgumentNullException>(identifier != null); return string.Format(CultureInfo.InvariantCulture, this.SigningHostFormat, identifier.Uri.Host, this.GetProxy(identifier).Host); }
/// <summary> /// Gets the URIs authorized to host host-meta documents on behalf of a given domain. /// </summary> /// <param name="identifier">The identifier.</param> /// <returns>A sequence of URIs that MAY provide the host-meta for a given identifier.</returns> private IEnumerable<HostMetaProxy> GetHostMetaLocations(UriIdentifier identifier) { Contract.Requires<ArgumentNullException>(identifier != null); // First try the proxies, as they are considered more "secure" than the local // host-meta for a domain since the domain may be defaced. IEnumerable<HostMetaProxy> result = this.TrustedHostMetaProxies; // Finally, look for the local host-meta. UriBuilder localHostMetaBuilder = new UriBuilder(); localHostMetaBuilder.Scheme = identifier.IsDiscoverySecureEndToEnd || identifier.Uri.IsTransportSecure() ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; localHostMetaBuilder.Host = identifier.Uri.Host; localHostMetaBuilder.Path = LocalHostMetaPath; result = result.Concat(new[] { new HostMetaProxy(localHostMetaBuilder.Uri.AbsoluteUri, identifier.Uri.Host) }); return result; }
/// <summary> /// Gets the location of the XRDS document that describes a given identifier. /// </summary> /// <param name="identifier">The identifier under discovery.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> /// <returns>An absolute URI, or <c>null</c> if one could not be determined.</returns> private Uri GetXrdsLocation(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { Contract.Requires<ArgumentNullException>(identifier != null); Contract.Requires<ArgumentNullException>(requestHandler != null); using (var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost)) { if (hostMetaResponse == null) { return null; } using (var sr = hostMetaResponse.GetResponseReader()) { string line = sr.ReadLine(); Match m = HostMetaLink.Match(line); if (m.Success) { Uri location = new Uri(m.Groups["location"].Value); Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri); return location; } } Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri); return null; } }
/// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="xrdsLocation">The location of the XRDS document to retrieve.</param> /// <returns> /// A HTTP response carrying an XRDS document. /// </returns> /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> private static IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, Uri xrdsLocation) { Contract.Requires<ArgumentNullException>(identifier != null); Contract.Requires<ArgumentNullException>(requestHandler != null); Contract.Requires<ArgumentNullException>(xrdsLocation != null); Contract.Ensures(Contract.Result<IncomingWebResponse>() != null); var request = (HttpWebRequest)WebRequest.Create(xrdsLocation); request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; request.Accept = ContentTypes.Xrds; var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None; var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) { Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType); } return response; }
public void TrailingPeriodsNotTrimmed() { TestAsFullAndPartialTrust(fullTrust => { string claimedIdentifier = "https://me.yahoo.com/a/AsDf.#asdf"; Identifier id = claimedIdentifier; Assert.AreEqual(claimedIdentifier, id.OriginalString); Assert.AreEqual(claimedIdentifier, id.ToString()); UriIdentifier idUri = new UriIdentifier(claimedIdentifier); Assert.AreEqual(claimedIdentifier, idUri.OriginalString); Assert.AreEqual(claimedIdentifier, idUri.ToString()); if (fullTrust) { Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri); } Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString()); Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString); Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed."); idUri = new UriIdentifier(new Uri(claimedIdentifier)); Assert.AreEqual(claimedIdentifier, idUri.OriginalString); Assert.AreEqual(claimedIdentifier, idUri.ToString()); if (fullTrust) { Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri); } Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString()); Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString); Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed."); claimedIdentifier = "https://me.yahoo.com:443/a/AsDf.#asdf"; id = claimedIdentifier; Assert.AreEqual(claimedIdentifier, id.OriginalString); Assert.AreEqual("https://me.yahoo.com/a/AsDf.#asdf", id.ToString()); }); }
public void DoesNotStripFragment() { Uri original = new Uri("http://a/b#c"); UriIdentifier identifier = new UriIdentifier(original); Assert.AreEqual(original.Fragment, identifier.Uri.Fragment); }
public void CtorUriHttpsSchemeSecure() { var uri = new UriIdentifier(new Uri("https://host/path"), true); Assert.AreEqual("https://host/path", uri.Uri.AbsoluteUri); Assert.IsTrue(uri.IsDiscoverySecureEndToEnd); }
public void UnicodeHostSupport() { var id = new UriIdentifier("http://server崎/村"); Assert.AreEqual("server崎", id.Uri.Host); }
public void TryRequireSslAdjustsIdentifier() { Identifier secureId; // Try Parse and ctor without explicit scheme var id = Identifier.Parse("www.yahoo.com"); Assert.AreEqual("http://www.yahoo.com/", id.ToString()); Assert.IsTrue(id.TryRequireSsl(out secureId)); Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd); Assert.AreEqual("https://www.yahoo.com/", secureId.ToString()); id = new UriIdentifier("www.yahoo.com"); Assert.AreEqual("http://www.yahoo.com/", id.ToString()); Assert.IsTrue(id.TryRequireSsl(out secureId)); Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd); Assert.AreEqual("https://www.yahoo.com/", secureId.ToString()); // Try Parse and ctor with explicit http:// scheme id = Identifier.Parse("http://www.yahoo.com"); Assert.IsFalse(id.TryRequireSsl(out secureId)); Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd, "Although the TryRequireSsl failed, the created identifier should retain the Ssl status."); Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); Assert.AreEqual(0, Discover(secureId).Count(), "Since TryRequireSsl failed, the created Identifier should never discover anything."); id = new UriIdentifier("http://www.yahoo.com"); Assert.IsFalse(id.TryRequireSsl(out secureId)); Assert.IsTrue(secureId.IsDiscoverySecureEndToEnd); Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); Assert.AreEqual(0, Discover(secureId).Count()); }
////[Test, Ignore("The spec says http:// must be prepended in this case, but that just creates an invalid URI. Our UntrustedWebRequest will stop disallowed schemes.")] public void CtorDisallowedScheme() { UriIdentifier id = new UriIdentifier(new Uri("ftp://host/path")); Assert.AreEqual("http://ftp://host/path", id.ToString()); Assert.IsTrue(id.SchemeImplicitlyPrepended); }
public void HttpSchemePrepended() { UriIdentifier id = new UriIdentifier("www.yahoo.com"); Assert.AreEqual("http://www.yahoo.com/", id.ToString()); Assert.IsTrue(id.SchemeImplicitlyPrepended); }
public void DiscoveryRequireSslWithInsecureXrdsInSecureHttpHeader() { var insecureXrdsSource = this.GetMockIdentifier(ProtocolVersion.V20, false); string html = "<html><head></head><body></body></html>"; WebHeaderCollection headers = new WebHeaderCollection { { "X-XRDS-Location", insecureXrdsSource } }; this.MockResponder.RegisterMockResponse(VanityUriSsl, VanityUriSsl, "text/html", headers, html); Identifier userSuppliedIdentifier = new UriIdentifier(VanityUriSsl, true); Assert.AreEqual(0, this.Discover(userSuppliedIdentifier).Count()); }
/// <summary> /// Gets the services for an identifier that are described by an external XRDS document. /// </summary> /// <param name="xrds">The XRD elements to search for described-by services.</param> /// <param name="identifier">The identifier under discovery.</param> /// <param name="requestHandler">The request handler.</param> /// <returns>The discovered services.</returns> private static IEnumerable <IdentifierDiscoveryResult> GetExternalServices(IEnumerable <XrdElement> xrds, UriIdentifier identifier, IDirectWebRequestHandler requestHandler) { Contract.Requires <ArgumentNullException>(xrds != null); Contract.Requires <ArgumentNullException>(identifier != null); Contract.Requires <ArgumentNullException>(requestHandler != null); Contract.Ensures(Contract.Result <IEnumerable <IdentifierDiscoveryResult> >() != null); var results = new List <IdentifierDiscoveryResult>(); foreach (var serviceElement in GetDescribedByServices(xrds)) { var templateNode = serviceElement.Node.SelectSingleNode("google:URITemplate", serviceElement.XmlNamespaceResolver); var nextAuthorityNode = serviceElement.Node.SelectSingleNode("google:NextAuthority", serviceElement.XmlNamespaceResolver); if (templateNode != null) { Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri))); string nextAuthority = nextAuthorityNode != null?nextAuthorityNode.Value.Trim() : identifier.Uri.Host; try { var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation); XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream)); ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority); results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier)); } catch (ProtocolException ex) { Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); } catch (XmlException ex) { Logger.Yadis.ErrorFormat("Error while parsing described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); } } } return(results); }
/// <summary> /// Gets the absolute proxy URI. /// </summary> /// <param name="identifier">The identifier being discovered.</param> /// <returns>The an absolute URI.</returns> public virtual Uri GetProxy(UriIdentifier identifier) { Contract.Requires <ArgumentNullException>(identifier != null); return(new Uri(string.Format(CultureInfo.InvariantCulture, this.ProxyFormat, Uri.EscapeDataString(identifier.Uri.Host)))); }
/// <summary> /// Validates the XML digital signature on an XRDS document. /// </summary> /// <param name="document">The XRDS document whose signature should be validated.</param> /// <param name="identifier">The identifier under discovery.</param> /// <param name="response">The response.</param> /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> /// <exception cref="ProtocolException">Thrown if the XRDS document has an invalid or a missing signature.</exception> private static void ValidateXmlDSig(XrdsDocument document, UriIdentifier identifier, IncomingWebResponse response, string signingHost) { Contract.Requires<ArgumentNullException>(document != null); Contract.Requires<ArgumentNullException>(identifier != null); Contract.Requires<ArgumentNullException>(response != null); var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver); ErrorUtilities.VerifyProtocol(signatureNode != null, OpenIdStrings.MissingElement, "Signature"); var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver); ErrorUtilities.VerifyProtocol(signedInfoNode != null, OpenIdStrings.MissingElement, "SignedInfo"); ErrorUtilities.VerifyProtocol( signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null, "Unrecognized or missing canonicalization method."); ErrorUtilities.VerifyProtocol( signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null, "Unrecognized or missing signature method."); var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver); 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); } } // Verify that the certificate is issued to the host on whom we are performing discovery. string hostName = certs[0].GetNameInfo(X509NameType.DnsName, false); ErrorUtilities.VerifyProtocol(string.Equals(hostName, signingHost, StringComparison.OrdinalIgnoreCase), "X.509 signing certificate issued to {0}, but a certificate for {1} was expected.", hostName, signingHost); // Verify the signature itself byte[] signature = Convert.FromBase64String(response.Headers["Signature"]); var provider = (RSACryptoServiceProvider)certs.First().PublicKey.Key; byte[] data = new byte[response.ResponseStream.Length]; response.ResponseStream.Seek(0, SeekOrigin.Begin); response.ResponseStream.Read(data, 0, data.Length); ErrorUtilities.VerifyProtocol(provider.VerifyData(data, "SHA1", signature), "Invalid XmlDSig signature on XRDS document."); }
/// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// A HTTP response carrying an XRDS document, or <c>null</c> if one could not be obtained. /// </returns> /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> private async Task <ResultWithSigningHost <HttpResponseMessage> > GetXrdsResponseAsync(UriIdentifier identifier, CancellationToken cancellationToken) { Requires.NotNull(identifier, "identifier"); var result = await this.GetXrdsLocationAsync(identifier, cancellationToken); if (result.Result == null) { return(new ResultWithSigningHost <HttpResponseMessage>()); } var response = await this.GetXrdsResponseAsync(identifier, result.Result, cancellationToken); return(new ResultWithSigningHost <HttpResponseMessage>(response, result.SigningHost)); }
/// <summary> /// Gets the XRDS HTTP response for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> /// <returns>A HTTP response carrying an XRDS document, or <c>null</c> if one could not be obtained.</returns> /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception> private IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { Contract.Requires<ArgumentNullException>(identifier != null); Contract.Requires<ArgumentNullException>(requestHandler != null); Uri xrdsLocation = this.GetXrdsLocation(identifier, requestHandler, out signingHost); if (xrdsLocation == null) { return null; } var response = GetXrdsResponse(identifier, requestHandler, xrdsLocation); return response; }
/// <summary> /// Searches HTML for the HEAD META tags that describe OpenID provider services. /// </summary> /// <param name="claimedIdentifier">The final URL that provided this HTML document. /// This may not be the same as (this) userSuppliedIdentifier if the /// userSuppliedIdentifier pointed to a 301 Redirect.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <param name="html">The HTML that was downloaded and should be searched.</param> /// <returns> /// A sequence of any discovered ServiceEndpoints. /// </returns> private static IEnumerable<IdentifierDiscoveryResult> DiscoverFromHtml(Uri claimedIdentifier, UriIdentifier userSuppliedIdentifier, string html) { var linkTags = new List<HtmlLink>(HtmlParser.HeadTags<HtmlLink>(html)); foreach (var protocol in Protocol.AllPracticalVersions) { // rel attributes are supposed to be interpreted with case INsensitivity, // and is a space-delimited list of values. (http://www.htmlhelp.com/reference/html40/values.html#linktypes) var serverLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryProviderKey) + @"\b", RegexOptions.IgnoreCase)); if (serverLinkTag == null) { continue; } Uri providerEndpoint = null; if (Uri.TryCreate(serverLinkTag.Href, UriKind.Absolute, out providerEndpoint)) { // See if a LocalId tag of the discovered version exists Identifier providerLocalIdentifier = null; var delegateLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryLocalIdKey) + @"\b", RegexOptions.IgnoreCase)); if (delegateLinkTag != null) { if (Identifier.IsValid(delegateLinkTag.Href)) { providerLocalIdentifier = delegateLinkTag.Href; } else { Logger.Yadis.WarnFormat("Skipping endpoint data because local id is badly formed ({0}).", delegateLinkTag.Href); continue; // skip to next version } } // Choose the TypeURI to match the OpenID version detected. string[] typeURIs = { protocol.ClaimedIdentifierServiceTypeURI }; yield return IdentifierDiscoveryResult.CreateForClaimedIdentifier( claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, new ProviderEndpointDescription(providerEndpoint, typeURIs), (int?)null, (int?)null); } } }
/// <summary> /// Gets the host-meta for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param> /// <returns> /// The host-meta response, or <c>null</c> if no host-meta document could be obtained. /// </returns> private IncomingWebResponse GetHostMeta(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) { Contract.Requires<ArgumentNullException>(identifier != null); Contract.Requires<ArgumentNullException>(requestHandler != null); foreach (var hostMetaProxy in this.GetHostMetaLocations(identifier)) { var hostMetaLocation = hostMetaProxy.GetProxy(identifier); var request = (HttpWebRequest)WebRequest.Create(hostMetaLocation); request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy; var options = DirectWebRequestOptions.AcceptAllHttpResponses; if (identifier.IsDiscoverySecureEndToEnd) { options |= DirectWebRequestOptions.RequireSsl; } var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan); try { if (response.Status == HttpStatusCode.OK) { Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation); signingHost = hostMetaProxy.GetSigningHost(identifier); return response; } else { Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation); response.Dispose(); } } catch { response.Dispose(); throw; } } signingHost = null; return null; }
/// <summary> /// Generates the OpenID Providers that are capable of asserting ownership /// of a particular URI claimed identifier. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="claimedIdentifier">The claimed identifier.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <returns> /// A sequence of the providers that can assert ownership of the given identifier. /// </returns> private static IEnumerable <IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this IEnumerable <XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { Requires.NotNull(xrds, "xrds"); Requires.NotNull(claimedIdentifier, "claimedIdentifier"); return(from service in xrds.FindClaimedIdentifierServices() from uri in service.UriElements where uri.Uri != null let providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) select IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority)); }
/// <summary> /// Gets the absolute proxy URI. /// </summary> /// <param name="identifier">The identifier being discovered.</param> /// <returns>The an absolute URI.</returns> public virtual Uri GetProxy(UriIdentifier identifier) { Contract.Requires<ArgumentNullException>(identifier != null); return new Uri(string.Format(CultureInfo.InvariantCulture, this.ProxyFormat, Uri.EscapeDataString(identifier.Uri.Host))); }
public static bool IsValid(string identifier) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(identifier)); return(XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier)); }
public void DiscoveryRequiresSslIgnoresInsecureEndpointsInXrds() { var insecureEndpoint = GetServiceEndpoint(0, ProtocolVersion.V20, 10, false); var secureEndpoint = GetServiceEndpoint(1, ProtocolVersion.V20, 20, true); UriIdentifier secureClaimedId = new UriIdentifier(VanityUriSsl, true); this.MockResponder.RegisterMockXrdsResponse(secureClaimedId, new IdentifierDiscoveryResult[] { insecureEndpoint, secureEndpoint }); Assert.AreEqual(secureEndpoint.ProviderLocalIdentifier, this.Discover(secureClaimedId).Single().ProviderLocalIdentifier); }
/// <summary> /// Gets the services for an identifier that are described by an external XRDS document. /// </summary> /// <param name="xrds">The XRD elements to search for described-by services.</param> /// <param name="identifier">The identifier under discovery.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The discovered services. /// </returns> private async Task <IEnumerable <IdentifierDiscoveryResult> > GetExternalServicesAsync(IEnumerable <XrdElement> xrds, UriIdentifier identifier, CancellationToken cancellationToken) { Requires.NotNull(xrds, "xrds"); Requires.NotNull(identifier, "identifier"); var results = new List <IdentifierDiscoveryResult>(); foreach (var serviceElement in GetDescribedByServices(xrds)) { var templateNode = serviceElement.Node.SelectSingleNode("google:URITemplate", serviceElement.XmlNamespaceResolver); var nextAuthorityNode = serviceElement.Node.SelectSingleNode("google:NextAuthority", serviceElement.XmlNamespaceResolver); if (templateNode != null) { Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri))); string nextAuthority = nextAuthorityNode != null?nextAuthorityNode.Value.Trim() : identifier.Uri.Host; try { using (var externalXrdsResponse = await this.GetXrdsResponseAsync(identifier, externalLocation, cancellationToken)) { var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings(); var responseStream = await externalXrdsResponse.Content.ReadAsStreamAsync(); XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(responseStream, readerSettings)); await ValidateXmlDSigAsync(externalXrds, identifier, externalXrdsResponse, nextAuthority); results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier)); } } catch (ProtocolException ex) { Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); } catch (XmlException ex) { Logger.Yadis.ErrorFormat("Error while parsing described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); } } } return(results); }
public static bool IsValid(string identifier) { return(XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier)); }
/// <summary> /// Creates the service endpoints described in this document, useful for requesting /// authentication of one of the OpenID Providers that result from it. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <returns> /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. /// </returns> internal static IEnumerable <IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable <XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { Contract.Requires <ArgumentNullException>(xrds != null); Contract.Requires <ArgumentNullException>(claimedIdentifier != null); Contract.Requires <ArgumentNullException>(userSuppliedIdentifier != null); Contract.Ensures(Contract.Result <IEnumerable <IdentifierDiscoveryResult> >() != null); var endpoints = new List <IdentifierDiscoveryResult>(); endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); Logger.Yadis.Debug(endpoints.ToStringDeferred(true)); return(endpoints); }
/// <summary> /// Generates the OpenID Providers that are capable of asserting ownership /// of a particular URI claimed identifier. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="claimedIdentifier">The claimed identifier.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <returns> /// A sequence of the providers that can assert ownership of the given identifier. /// </returns> private static IEnumerable <ServiceEndpoint> GenerateClaimedIdentifierServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { return(from service in xrds.FindClaimedIdentifierServices() from uri in service.UriElements where uri.Uri != null let providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) select ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority)); }
/// <summary> /// Gets the host-meta for a given identifier. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The host-meta response, or <c>null</c> if no host-meta document could be obtained. /// </returns> private async Task <ResultWithSigningHost <HttpResponseMessage> > GetHostMetaAsync(UriIdentifier identifier, CancellationToken cancellationToken) { Requires.NotNull(identifier, "identifier"); using (var httpClient = this.HostFactories.CreateHttpClient(identifier.IsDiscoverySecureEndToEnd, Yadis.IdentifierDiscoveryCachePolicy)) { foreach (var hostMetaProxy in this.GetHostMetaLocations(identifier)) { var hostMetaLocation = hostMetaProxy.GetProxy(identifier); var response = await httpClient.GetAsync(hostMetaLocation, cancellationToken); try { if (response.IsSuccessStatusCode) { Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation); return(new ResultWithSigningHost <HttpResponseMessage>(response, hostMetaProxy.GetSigningHost(identifier))); } else { Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation); response.Dispose(); } } catch { response.Dispose(); throw; } } } return(new ResultWithSigningHost <HttpResponseMessage>()); }
public void DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags() { var insecureXrdsSource = this.GetMockIdentifier(ProtocolVersion.V20, false); string html = string.Format( @" <html><head> <meta http-equiv='X-XRDS-Location' content='{0}'/> <!-- this one will be insecure and ignored --> <link rel='openid2.provider' href='{1}' /> <link rel='openid2.local_id' href='{2}' /> </head><body></body></html>", HttpUtility.HtmlEncode(insecureXrdsSource), HttpUtility.HtmlEncode(OPUriSsl.AbsoluteUri), HttpUtility.HtmlEncode(OPLocalIdentifiersSsl[1].AbsoluteUri)); this.MockResponder.RegisterMockResponse(VanityUriSsl, "text/html", html); Identifier userSuppliedIdentifier = new UriIdentifier(VanityUriSsl, true); // We verify that the XRDS was ignored and the LINK tags were used // because the XRDS OP-LocalIdentifier uses different local identifiers. Assert.AreEqual(OPLocalIdentifiersSsl[1].AbsoluteUri, this.Discover(userSuppliedIdentifier).Single().ProviderLocalIdentifier.ToString()); }
/// <summary> /// Gets the services for an identifier that are described by an external XRDS document. /// </summary> /// <param name="xrds">The XRD elements to search for described-by services.</param> /// <param name="identifier">The identifier under discovery.</param> /// <param name="requestHandler">The request handler.</param> /// <returns>The discovered services.</returns> private static IEnumerable<IdentifierDiscoveryResult> GetExternalServices(IEnumerable<XrdElement> xrds, UriIdentifier identifier, IDirectWebRequestHandler requestHandler) { Contract.Requires<ArgumentNullException>(xrds != null); Contract.Requires<ArgumentNullException>(identifier != null); Contract.Requires<ArgumentNullException>(requestHandler != null); Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); var results = new List<IdentifierDiscoveryResult>(); foreach (var serviceElement in GetDescribedByServices(xrds)) { var templateNode = serviceElement.Node.SelectSingleNode("google:URITemplate", serviceElement.XmlNamespaceResolver); var nextAuthorityNode = serviceElement.Node.SelectSingleNode("google:NextAuthority", serviceElement.XmlNamespaceResolver); if (templateNode != null) { Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri))); string nextAuthority = nextAuthorityNode != null ? nextAuthorityNode.Value.Trim() : identifier.Uri.Host; try { using (var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation)) { XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream)); ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority); results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier)); } } catch (ProtocolException ex) { Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); } catch (XmlException ex) { Logger.Yadis.ErrorFormat("Error while parsing described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex); } } } return results; }
/// <summary> /// Gets the absolute proxy URI. /// </summary> /// <param name="identifier">The identifier being discovered.</param> /// <returns>The an absolute URI.</returns> public virtual Uri GetProxy(UriIdentifier identifier) { Requires.NotNull(identifier, "identifier"); return(new Uri(string.Format(CultureInfo.InvariantCulture, this.ProxyFormat, Uri.EscapeDataString(identifier.Uri.Host)))); }
/// <summary> /// Searches HTML for the HEAD META tags that describe OpenID provider services. /// </summary> /// <param name="claimedIdentifier">The final URL that provided this HTML document. /// This may not be the same as (this) userSuppliedIdentifier if the /// userSuppliedIdentifier pointed to a 301 Redirect.</param> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> /// <param name="html">The HTML that was downloaded and should be searched.</param> /// <returns> /// A sequence of any discovered ServiceEndpoints. /// </returns> private static IEnumerable <IdentifierDiscoveryResult> DiscoverFromHtml(Uri claimedIdentifier, UriIdentifier userSuppliedIdentifier, string html) { var linkTags = new List <HtmlLink>(HtmlParser.HeadTags <HtmlLink>(html)); foreach (var protocol in Protocol.AllPracticalVersions) { // rel attributes are supposed to be interpreted with case INsensitivity, // and is a space-delimited list of values. (http://www.htmlhelp.com/reference/html40/values.html#linktypes) var serverLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryProviderKey) + @"\b", RegexOptions.IgnoreCase)); if (serverLinkTag == null) { continue; } Uri providerEndpoint = null; if (Uri.TryCreate(serverLinkTag.Href, UriKind.Absolute, out providerEndpoint)) { // See if a LocalId tag of the discovered version exists Identifier providerLocalIdentifier = null; var delegateLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryLocalIdKey) + @"\b", RegexOptions.IgnoreCase)); if (delegateLinkTag != null) { if (Identifier.IsValid(delegateLinkTag.Href)) { providerLocalIdentifier = delegateLinkTag.Href; } else { Logger.Yadis.WarnFormat("Skipping endpoint data because local id is badly formed ({0}).", delegateLinkTag.Href); continue; // skip to next version } } // Choose the TypeURI to match the OpenID version detected. string[] typeURIs = { protocol.ClaimedIdentifierServiceTypeURI }; yield return(IdentifierDiscoveryResult.CreateForClaimedIdentifier( claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, new ProviderEndpointDescription(providerEndpoint, typeURIs), (int?)null, (int?)null)); } } }
/// <summary> /// Gets the signing host URI. /// </summary> /// <param name="identifier">The identifier being discovered.</param> /// <returns>A host name.</returns> public virtual string GetSigningHost(UriIdentifier identifier) { Requires.NotNull(identifier, "identifier"); return(string.Format(CultureInfo.InvariantCulture, this.SigningHostFormat, identifier.Uri.Host, this.GetProxy(identifier).Host)); }
public static bool IsValid(string identifier) { Requires.NotNullOrEmpty(identifier, "identifier"); return(XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier)); }
/// <summary> /// Gets the signing host URI. /// </summary> /// <param name="identifier">The identifier being discovered.</param> /// <returns>A host name.</returns> public virtual string GetSigningHost(UriIdentifier identifier) { Contract.Requires <ArgumentNullException>(identifier != null); return(string.Format(CultureInfo.InvariantCulture, this.SigningHostFormat, identifier.Uri.Host, this.GetProxy(identifier).Host)); }
/// <summary> /// Converts a given identifier to its secure equivalent. /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. /// Discovery is made to require SSL for the entire resolution process. /// </summary> /// <param name="secureIdentifier">The newly created secure identifier. /// If the conversion fails, <paramref name="secureIdentifier"/> retains /// <i>this</i> identifiers identity, but will never discover any endpoints.</param> /// <returns> /// True if the secure conversion was successful. /// False if the Identifier was originally created with an explicit HTTP scheme. /// </returns> internal override bool TryRequireSsl(out Identifier secureIdentifier) { // If this Identifier is already secure, reuse it. if (IsDiscoverySecureEndToEnd) { secureIdentifier = this; return true; } // If this identifier already uses SSL for initial discovery, return one // that guarantees it will be used throughout the discovery process. if (string.Equals(Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { secureIdentifier = new UriIdentifier(this.Uri, true); return true; } // Otherwise, try to make this Identifier secure by normalizing to HTTPS instead of HTTP. if (this.SchemeImplicitlyPrepended) { UriBuilder newIdentifierUri = new UriBuilder(this.Uri); newIdentifierUri.Scheme = Uri.UriSchemeHttps; if (newIdentifierUri.Port == 80) { newIdentifierUri.Port = 443; } secureIdentifier = new UriIdentifier(newIdentifierUri.Uri, true); return true; } // This identifier is explicitly NOT https, so we cannot change it. secureIdentifier = new NoDiscoveryIdentifier(this, true); return false; }