/// <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> /// Searches for an XRDS document at the realm URL, and if found, searches /// for a description of a relying party endpoints (OpenId login pages). /// </summary> /// <param name="allowRedirects"> /// Whether redirects may be followed when discovering the Realm. /// This may be true when creating an unsolicited assertion, but must be /// false when performing return URL verification per 2.0 spec section 9.2.1. /// </param> /// <returns>The details of the endpoints if found, otherwise null.</returns> internal IEnumerable <DotNetOpenId.Provider.RelyingPartyReceivingEndpoint> Discover(bool allowRedirects) { // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Yadis.Discover(UriWithWildcardChangedToWww, false); if (yadisResult != null) { if (!allowRedirects && yadisResult.NormalizedUri != yadisResult.RequestUri) { // Redirect occurred when it was not allowed. throw new OpenIdException(string.Format(CultureInfo.CurrentCulture, Strings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri)); } if (yadisResult.IsXrds) { try { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); return(xrds.FindRelyingPartyReceivingEndpoints()); } catch (XmlException ex) { throw new OpenIdException(Strings.InvalidXRDSDocument, ex); } } } return(new RelyingPartyReceivingEndpoint[0]); }
/// <summary> /// Performs discovery on the specified identifier. /// </summary> /// <param name="identifier">The identifier to perform discovery on.</param> /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> /// <returns> /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. /// </returns> public IEnumerable <IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { abortDiscoveryChain = false; var uriIdentifier = identifier as UriIdentifier; if (uriIdentifier == null) { return(Enumerable.Empty <IdentifierDiscoveryResult>()); } var endpoints = new List <IdentifierDiscoveryResult>(); // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Discover(requestHandler, uriIdentifier, identifier.IsDiscoverySecureEndToEnd); if (yadisResult != null) { if (yadisResult.IsXrds) { try { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); var xrdsEndpoints = xrds.XrdElements.CreateServiceEndpoints(yadisResult.NormalizedUri, uriIdentifier); // Filter out insecure endpoints if high security is required. if (uriIdentifier.IsDiscoverySecureEndToEnd) { xrdsEndpoints = xrdsEndpoints.Where(se => se.ProviderEndpoint.IsTransportSecure()); } endpoints.AddRange(xrdsEndpoints); } catch (XmlException ex) { Logger.Yadis.Error("Error while parsing the XRDS document. Falling back to HTML discovery.", ex); } } // Failing YADIS discovery of an XRDS document, we try HTML discovery. if (endpoints.Count == 0) { var htmlEndpoints = new List <IdentifierDiscoveryResult>(DiscoverFromHtml(yadisResult.NormalizedUri, uriIdentifier, yadisResult.ResponseText)); if (htmlEndpoints.Any()) { Logger.Yadis.DebugFormat("Total services discovered in HTML: {0}", htmlEndpoints.Count); Logger.Yadis.Debug(htmlEndpoints.ToStringDeferred(true)); endpoints.AddRange(htmlEndpoints.Where(ep => !uriIdentifier.IsDiscoverySecureEndToEnd || ep.ProviderEndpoint.IsTransportSecure())); if (endpoints.Count == 0) { Logger.Yadis.Info("No HTML discovered endpoints met the security requirements."); } } else { Logger.Yadis.Debug("HTML discovery failed to find any endpoints."); } } else { Logger.Yadis.Debug("Skipping HTML discovery because XRDS contained service endpoints."); } } return(endpoints); }
/// <summary> /// Enumerates the XRDS service elements that describe OpenID Relying Party return_to URLs /// that can receive authentication assertions. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <returns>A sequence of service elements.</returns> private static IEnumerable <ServiceElement> FindReturnToServices(this XrdsDocument xrds) { Requires.NotNull(xrds, "xrds"); return(from xrd in xrds.XrdElements from service in xrd.OpenIdRelyingPartyReturnToServices select service); }
/// <summary> /// Searches for an XRDS document at the realm URL, and if found, searches /// for a description of a relying party endpoints (OpenId login pages). /// </summary> /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. /// This may be true when creating an unsolicited assertion, but must be /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> /// <returns> /// The details of the endpoints if found; or <c>null</c> if no service document was discovered. /// </returns> internal virtual IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) { XrdsDocument xrds = this.Discover(requestHandler, allowRedirects); if (xrds != null) { return xrds.FindRelyingPartyReceivingEndpoints(); } return null; }
/// <summary> /// Finds the icons the relying party wants an OP to display as part of authentication, /// per the UI extension spec. /// </summary> /// <param name="xrds">The XrdsDocument to search.</param> /// <returns>A sequence of the icon URLs in preferred order.</returns> internal static IEnumerable <Uri> FindRelyingPartyIcons(this XrdsDocument xrds) { Requires.NotNull(xrds, "xrds"); return(from xrd in xrds.XrdElements from service in xrd.OpenIdRelyingPartyIcons from uri in service.UriElements select uri.Uri); }
/// <summary> /// Enumerates the XRDS service elements that describe OpenID Relying Party return_to URLs /// that can receive authentication assertions. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <returns>A sequence of service elements.</returns> private static IEnumerable <ServiceElement> FindReturnToServices(this XrdsDocument xrds) { Contract.Requires <ArgumentNullException>(xrds != null); Contract.Ensures(Contract.Result <IEnumerable <ServiceElement> >() != null); return(from xrd in xrds.XrdElements from service in xrd.OpenIdRelyingPartyReturnToServices select service); }
/// <summary> /// Downloads the XRDS document for this XRI. /// </summary> /// <param name="requestHandler">The request handler.</param> /// <returns>The XRDS document.</returns> private XrdsDocument DownloadXrds(IDirectWebRequestHandler requestHandler) { XrdsDocument doc; using (var xrdsResponse = Yadis.Request(requestHandler, this.XrdsUrl, this.IsDiscoverySecureEndToEnd)) { doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); } ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); return(doc); }
/// <summary> /// Finds the icons the relying party wants an OP to display as part of authentication, /// per the UI extension spec. /// </summary> /// <param name="xrds">The XrdsDocument to search.</param> /// <returns>A sequence of the icon URLs in preferred order.</returns> internal static IEnumerable <Uri> FindRelyingPartyIcons(this XrdsDocument xrds) { Contract.Requires <ArgumentNullException>(xrds != null); Contract.Ensures(Contract.Result <IEnumerable <Uri> >() != null); return(from xrd in xrds.XrdElements from service in xrd.OpenIdRelyingPartyIcons from uri in service.UriElements select uri.Uri); }
XrdsDocument downloadXrds() { var xrdsResponse = UntrustedWebRequest.Request(XrdsUrl); XrdsDocument doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); if (!doc.IsXrdResolutionSuccessful) { throw new OpenIdException(Strings.XriResolutionFailed); } return(doc); }
/// <summary> /// Searches for an XRDS document at the realm URL, and if found, searches /// for a description of a relying party endpoints (OpenId login pages). /// </summary> /// <param name="hostFactories">The host factories.</param> /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. /// This may be true when creating an unsolicited assertion, but must be /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The details of the endpoints if found; or <c>null</c> if no service document was discovered. /// </returns> internal virtual async Task <IEnumerable <RelyingPartyEndpointDescription> > DiscoverReturnToEndpointsAsync(IHostFactories hostFactories, bool allowRedirects, CancellationToken cancellationToken) { XrdsDocument xrds = await this.DiscoverAsync(hostFactories, allowRedirects, cancellationToken); if (xrds != null) { return(xrds.FindRelyingPartyReceivingEndpoints()); } return(null); }
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); }
/// <summary> /// Downloads the XRDS document for this XRI. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <returns>The XRDS document.</returns> private static XrdsDocument DownloadXrds(XriIdentifier identifier, IDirectWebRequestHandler requestHandler) { Requires.NotNull(identifier, "identifier"); Requires.NotNull(requestHandler, "requestHandler"); XrdsDocument doc; using (var xrdsResponse = Yadis.Request(requestHandler, GetXrdsUrl(identifier), identifier.IsDiscoverySecureEndToEnd)) { var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings(); doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream, readerSettings)); } ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); return(doc); }
/// <summary> /// Downloads the XRDS document for this XRI. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="requestHandler">The request handler.</param> /// <returns>The XRDS document.</returns> private static XrdsDocument DownloadXrds(XriIdentifier identifier, IDirectWebRequestHandler requestHandler) { Requires.NotNull(identifier, "identifier"); Requires.NotNull(requestHandler, "requestHandler"); Contract.Ensures(Contract.Result <XrdsDocument>() != null); XrdsDocument doc; using (var xrdsResponse = Yadis.Request(requestHandler, GetXrdsUrl(identifier), identifier.IsDiscoverySecureEndToEnd)) { doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); } ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); return(doc); }
internal override IEnumerable <ServiceEndpoint> Discover() { List <ServiceEndpoint> endpoints = new List <ServiceEndpoint>(); // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Yadis.Discover(this, IsDiscoverySecureEndToEnd); if (yadisResult != null) { if (yadisResult.IsXrds) { try { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri); // Filter out insecure endpoints if high security is required. if (IsDiscoverySecureEndToEnd) { xrdsEndpoints = Util.Where(xrdsEndpoints, se => se.IsSecure); } endpoints.AddRange(xrdsEndpoints); } catch (XmlException ex) { //Logger.Error("Error while parsing the XRDS document. Falling back to HTML discovery.", ex); } } // Failing YADIS discovery of an XRDS document, we try HTML discovery. if (endpoints.Count == 0) { var htmlEndpoints = new List <ServiceEndpoint>(DiscoverFromHtml(yadisResult.NormalizedUri, yadisResult.ResponseText)); if (htmlEndpoints.Count > 0) { //Logger.DebugFormat("Total services discovered in HTML: {0}", htmlEndpoints.Count); //Logger.Debug(Util.ToString(htmlEndpoints, true)); endpoints.AddRange(Util.Where(htmlEndpoints, ep => !IsDiscoverySecureEndToEnd || ep.IsSecure)); if (endpoints.Count == 0) { //Logger.Info("No HTML discovered endpoints met the security requirements."); } } else { //Logger.Debug("HTML discovery failed to find any endpoints."); } } else { //Logger.Debug("Skipping HTML discovery because XRDS contained service endpoints."); } } return(endpoints); }
/// <summary> /// Performs discovery on the Identifier. /// </summary> /// <param name="requestHandler">The web request handler to use for discovery.</param> /// <returns> /// An initialized structure containing the discovered provider endpoint information. /// </returns> internal override IEnumerable <ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler) { List <ServiceEndpoint> endpoints = new List <ServiceEndpoint>(); // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this, IsDiscoverySecureEndToEnd); if (yadisResult != null) { if (yadisResult.IsXrds) { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri, this); // Filter out insecure endpoints if high security is required. if (IsDiscoverySecureEndToEnd) { xrdsEndpoints = xrdsEndpoints.Where(se => se.IsSecure); } endpoints.AddRange(xrdsEndpoints); } // Failing YADIS discovery of an XRDS document, we try HTML discovery. if (endpoints.Count == 0) { var htmlEndpoints = new List <ServiceEndpoint>(DiscoverFromHtml(yadisResult.NormalizedUri, this, yadisResult.ResponseText)); if (htmlEndpoints.Any()) { Logger.DebugFormat("Total services discovered in HTML: {0}", htmlEndpoints.Count); Logger.Debug(htmlEndpoints.ToStringDeferred(true)); endpoints.AddRange(htmlEndpoints.Where(ep => !IsDiscoverySecureEndToEnd || ep.IsSecure)); if (endpoints.Count == 0) { Logger.Info("No HTML discovered endpoints met the security requirements."); } } else { Logger.Debug("HTML discovery failed to find any endpoints."); } } else { Logger.Debug("Skipping HTML discovery because XRDS contained service endpoints."); } } return(endpoints); }
internal override IEnumerable <ServiceEndpoint> Discover() { List <ServiceEndpoint> endpoints = new List <ServiceEndpoint>(); // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Yadis.Discover(this, IsDiscoverySecureEndToEnd); if (yadisResult != null) { if (yadisResult.IsXrds) { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri); // Filter out insecure endpoints if high security is required. if (IsDiscoverySecureEndToEnd) { xrdsEndpoints = Util.Where(xrdsEndpoints, se => se.IsSecure); } endpoints.AddRange(xrdsEndpoints); } // Failing YADIS discovery of an XRDS document, we try HTML discovery. if (endpoints.Count == 0) { ServiceEndpoint ep = DiscoverFromHtml(yadisResult.NormalizedUri, yadisResult.ResponseText); if (ep != null) { Logger.Debug("HTML discovery found a service endpoint."); Logger.Debug(ep); if (!IsDiscoverySecureEndToEnd || ep.IsSecure) { endpoints.Add(ep); } else { Logger.Info("Skipping HTML discovered endpoint because it is not secure."); } } else { Logger.Debug("HTML discovery failed to find any endpoints."); } } else { Logger.Debug("Skipping HTML discovery because XRDS contained service endpoints."); } } return(endpoints); }
/// <summary> /// Performs discovery on the specified identifier. /// </summary> /// <param name="identifier">The identifier to perform discovery on.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. /// </returns> public async Task <IdentifierDiscoveryServiceResult> DiscoverAsync(Identifier identifier, CancellationToken cancellationToken) { Requires.NotNull(identifier, "identifier"); Verify.Operation(this.HostFactories != null, Strings.HostFactoriesRequired); cancellationToken.ThrowIfCancellationRequested(); // Google Apps are always URIs -- not XRIs. var uriIdentifier = identifier as UriIdentifier; if (uriIdentifier == null) { return(new IdentifierDiscoveryServiceResult(Enumerable.Empty <IdentifierDiscoveryResult>())); } var results = new List <IdentifierDiscoveryResult>(); using (var response = await this.GetXrdsResponseAsync(uriIdentifier, cancellationToken)) { if (response.Result != null) { try { var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings(); var responseStream = await response.Result.Content.ReadAsStreamAsync(); var document = new XrdsDocument(XmlReader.Create(responseStream, readerSettings)); await ValidateXmlDSigAsync(document, uriIdentifier, response.Result, response.SigningHost); var xrds = GetXrdElements(document, uriIdentifier.Uri.Host); // Look for claimed identifier template URIs for an additional XRDS document. results.AddRange(await this.GetExternalServicesAsync(xrds, uriIdentifier, cancellationToken)); // If we couldn't find any claimed identifiers, look for OP identifiers. // Normally this would be the opposite (OP Identifiers take precedence over // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers, // which would break positive assertion checks). if (results.Count == 0) { results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier)); } } catch (XmlException ex) { Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.Result.RequestMessage.RequestUri, ex); } } } return(new IdentifierDiscoveryServiceResult(results, abortDiscoveryChain: true)); }
/// <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="userSuppliedIdentifier">The user-supplied i-name that was used to discover this XRDS document.</param> /// <returns>A sequence of OpenID Providers that can assert ownership of the canonical ID given in this document.</returns> internal static IEnumerable <ServiceEndpoint> CreateServiceEndpoints(this XrdsDocument xrds, XriIdentifier 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(userSuppliedIdentifier)); } Logger.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count); Logger.Debug(endpoints.ToStringDeferred(true)); return(endpoints); }
/// <summary> /// Gets the URL of the RP icon for the OP to display. /// </summary> /// <param name="realm">The realm of the RP where the authentication request originated.</param> /// <param name="hostFactories">The host factories.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. /// </returns> /// <value>The icon URL.</value> /// <remarks> /// This property is automatically set for the OP with the result of RP discovery. /// RPs should set this value by including an entry such as this in their XRDS document. /// <example> /// <Service xmlns="xri://$xrd*($v*2.0)"> /// <Type>http://specs.openid.net/extensions/ui/icon</Type> /// <URI>http://consumer.example.com/images/image.jpg</URI> /// </Service> /// </example> /// </remarks> public static async Task <IEnumerable <Uri> > GetRelyingPartyIconUrlsAsync(Realm realm, IHostFactories hostFactories, CancellationToken cancellationToken) { Requires.NotNull(realm, "realm"); Requires.NotNull(hostFactories, "hostFactories"); XrdsDocument xrds = await realm.DiscoverAsync(hostFactories, false, cancellationToken); if (xrds == null) { return(Enumerable.Empty <Uri>()); } else { return(xrds.FindRelyingPartyIcons()); } }
/// <summary> /// Performs discovery on the specified identifier. /// </summary> /// <param name="identifier">The identifier to perform discovery on.</param> /// <param name="requestHandler">The means to place outgoing HTTP requests.</param> /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param> /// <returns> /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty. /// </returns> public IEnumerable <IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) { abortDiscoveryChain = false; // Google Apps are always URIs -- not XRIs. var uriIdentifier = identifier as UriIdentifier; if (uriIdentifier == null) { return(Enumerable.Empty <IdentifierDiscoveryResult>()); } var results = new List <IdentifierDiscoveryResult>(); string signingHost; using (var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost)) { if (response != null) { try { var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings(); var document = new XrdsDocument(XmlReader.Create(response.ResponseStream, readerSettings)); ValidateXmlDSig(document, uriIdentifier, response, signingHost); var xrds = GetXrdElements(document, uriIdentifier.Uri.Host); // Look for claimed identifier template URIs for an additional XRDS document. results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler)); // If we couldn't find any claimed identifiers, look for OP identifiers. // Normally this would be the opposite (OP Identifiers take precedence over // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers, // which would break positive assertion checks). if (results.Count == 0) { results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier)); } abortDiscoveryChain = true; } catch (XmlException ex) { Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex); } } } return(results); }
public void CanGetYadisDocument() { YadisRequest request = new YadisRequest(Utility.GetTestUri("yadisexamples/index")); XrdsDocument document = request.GetYadisDocument(); Assert.IsNotNull(document); Assert.IsNotNull(document.XrdElements); Assert.AreEqual(1, document.XrdElements.Count); Assert.IsNotNull(document.XrdElements[0].Services); Assert.AreEqual(3, document.XrdElements[0].Services.Count); Assert.IsNotNull(document.XrdElements[0].Services[0].Types); Assert.AreEqual(5, document.XrdElements[0].Services[0].Types.Count); Assert.AreEqual("http://specs.openid.net/auth/2.0/signon", document.XrdElements[0].Services[0].Types[0]); Assert.AreEqual("http://jornwildt.myopenid.com/", document.XrdElements[0].Services[0].LocalID); Assert.IsNotNull(document.XrdElements[0].Services[0].Uris); Assert.AreEqual(1, document.XrdElements[0].Services[0].Uris.Count); Assert.AreEqual("http://www.myopenid.com/server", document.XrdElements[0].Services[0].Uris[0].Uri); }
/// <summary> /// Gets the URL of the RP icon for the OP to display. /// </summary> /// <param name="realm">The realm of the RP where the authentication request originated.</param> /// <param name="webRequestHandler">The web request handler to use for discovery. /// Usually available via <see cref="Channel.WebRequestHandler">OpenIdProvider.Channel.WebRequestHandler</see>.</param> /// <returns> /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. /// </returns> /// <value>The icon URL.</value> /// <remarks> /// This property is automatically set for the OP with the result of RP discovery. /// RPs should set this value by including an entry such as this in their XRDS document. /// <example> /// <Service xmlns="xri://$xrd*($v*2.0)"> /// <Type>http://specs.openid.net/extensions/ui/icon</Type> /// <URI>http://consumer.example.com/images/image.jpg</URI> /// </Service> /// </example> /// </remarks> public static IEnumerable <Uri> GetRelyingPartyIconUrls(Realm realm, IDirectWebRequestHandler webRequestHandler) { Contract.Requires(realm != null); Contract.Requires(webRequestHandler != null); ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); ErrorUtilities.VerifyArgumentNotNull(webRequestHandler, "webRequestHandler"); XrdsDocument xrds = realm.Discover(webRequestHandler, false); if (xrds == null) { return(Enumerable.Empty <Uri>()); } else { return(xrds.FindRelyingPartyIcons()); } }
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> /// Downloads the XRDS document for this XRI. /// </summary> /// <param name="identifier">The identifier.</param> /// <param name="hostFactories">The host factories.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The XRDS document. /// </returns> private static async Task <XrdsDocument> DownloadXrdsAsync(XriIdentifier identifier, IHostFactories hostFactories, CancellationToken cancellationToken) { Requires.NotNull(identifier, "identifier"); Requires.NotNull(hostFactories, "hostFactories"); XrdsDocument doc; using (var xrdsResponse = await Yadis.RequestAsync(GetXrdsUrl(identifier), identifier.IsDiscoverySecureEndToEnd, hostFactories, cancellationToken)) { xrdsResponse.EnsureSuccessStatusCode(); var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings(); ErrorUtilities.VerifyProtocol(xrdsResponse.Content != null, "XRDS request \"{0}\" returned no response.", GetXrdsUrl(identifier)); await xrdsResponse.Content.LoadIntoBufferAsync(); using (var xrdsStream = await xrdsResponse.Content.ReadAsStreamAsync()) { doc = new XrdsDocument(XmlReader.Create(xrdsStream, readerSettings)); } } ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); return(doc); }
/// <summary> /// Searches for an XRDS document at the realm URL, and if found, searches /// for a description of a relying party endpoints (OpenId login pages). /// </summary> /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. /// This may be true when creating an unsolicited assertion, but must be /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> /// <returns> /// The details of the endpoints if found, otherwise null. /// </returns> internal IEnumerable <RelyingPartyEndpointDescription> Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this.UriWithWildcardChangedToWww, false); if (yadisResult != null) { // Detect disallowed redirects, since realm discovery never allows them for security. ErrorUtilities.VerifyProtocol(allowRedirects || yadisResult.NormalizedUri == yadisResult.RequestUri, OpenIdStrings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri); if (yadisResult.IsXrds) { try { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); return(xrds.FindRelyingPartyReceivingEndpoints()); } catch (XmlException ex) { throw ErrorUtilities.Wrap(ex, XrdsStrings.InvalidXRDSDocument); } } } return(Enumerable.Empty <RelyingPartyEndpointDescription>()); }
/// <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); }
/// <summary> /// Gets the XRD elements that have a given CanonicalID. /// </summary> /// <param name="document">The XRDS document.</param> /// <param name="canonicalId">The CanonicalID to match on.</param> /// <returns>A sequence of XRD elements.</returns> private static IEnumerable <XrdElement> GetXrdElements(XrdsDocument document, string canonicalId) { // filter to include only those XRD elements describing the host whose host-meta pointed us to this document. return(document.XrdElements.Where(xrd => string.Equals(xrd.CanonicalID, canonicalId, StringComparison.Ordinal))); }
/// <summary> /// Generates OpenID Providers that can authenticate using directed identity. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <param name="opIdentifier">The OP Identifier entered (and resolved) by the user.</param> /// <returns>A sequence of the providers that can offer directed identity services.</returns> private static IEnumerable <ServiceEndpoint> GenerateOPIdentifierServiceEndpoints(this XrdsDocument xrds, Identifier opIdentifier) { return(from service in xrds.FindOPIdentifierServices() from uri in service.UriElements let protocol = Protocol.FindBestVersion(p => p.OPIdentifierServiceTypeURI, service.TypeElementUris) let providerDescription = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) select ServiceEndpoint.CreateForProviderIdentifier(opIdentifier, providerDescription, service.Priority, uri.Priority)); }
/// <summary> /// Finds the Relying Party return_to receiving endpoints. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> /// <returns>A sequence of Relying Party descriptors for the return_to endpoints.</returns> /// <remarks> /// This is useful for Providers to send unsolicited assertions to Relying Parties, /// or for Provider's to perform RP discovery/verification as part of authentication. /// </remarks> internal static IEnumerable <RelyingPartyEndpointDescription> FindRelyingPartyReceivingEndpoints(this XrdsDocument xrds) { Requires.NotNull(xrds, "xrds"); return(from service in xrds.FindReturnToServices() from uri in service.UriElements select new RelyingPartyEndpointDescription(uri.Uri, service.TypeElementUris)); }