// // disable StyleCop on this file //----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using DotNetOpenAuth.Messaging; using System.Globalization; using System.Diagnostics.CodeAnalysis; using System.Diagnostics; using Validation; /// /// An enumeration of the OpenID protocol versions supported by this library. /// public enum ProtocolVersion { /// /// OpenID Authentication 1.0 /// V10, /// /// OpenID Authentication 1.1 /// V11, /// /// OpenID Authentication 2.0 /// V20, } /// /// Tracks the several versions of OpenID this library supports and the unique /// constants to each version used in the protocol. /// [DebuggerDisplay("OpenID {Version}")] internal sealed class Protocol { /// /// The value of the openid.ns parameter in the OpenID 2.0 specification. /// internal const string OpenId2Namespace = "http://specs.openid.net/auth/2.0"; /// /// The parameter of the callback parameter we tack onto the return_to URL /// to store the replay-detection nonce. /// internal const string ReturnToNonceParameter = OpenIdUtilities.CustomParameterPrefix + "request_nonce"; /// /// Scans a list for matches with some element of the OpenID protocol, /// searching from newest to oldest protocol for the first and best match. /// /// The type of element retrieved from the instance. /// Takes a instance and returns an element of it. /// The list to scan for matches. /// The protocol with the element that matches some item in the list. internal static Protocol FindBestVersion(Func elementOf, IEnumerable list) { foreach (var protocol in Protocol.AllVersions) { foreach (var item in list) { if (item != null && item.Equals(elementOf(protocol))) return protocol; } } return null; } Protocol(QueryParameters queryBits) { openidnp = queryBits; openid = new QueryParameters(queryBits); } // Well-known, supported versions of the OpenID spec. public static readonly Protocol V10 = new Protocol(new QueryParameters()) { Version = new Version(1, 0), XmlNamespace = "http://openid.net/xmlns/1.0", QueryDeclaredNamespaceVersion = null, ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.0", OPIdentifierServiceTypeURI = null, // not supported ClaimedIdentifierForOPIdentifier = null, // not supported RPReturnToTypeURI = null, // not supported HtmlDiscoveryProviderKey = "openid.server", HtmlDiscoveryLocalIdKey = "openid.delegate", }; public static readonly Protocol V11 = new Protocol(new QueryParameters()) { Version = new Version(1, 1), XmlNamespace = "http://openid.net/xmlns/1.0", QueryDeclaredNamespaceVersion = null, ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.1", OPIdentifierServiceTypeURI = null, // not supported ClaimedIdentifierForOPIdentifier = null, // not supported RPReturnToTypeURI = null, // not supported HtmlDiscoveryProviderKey = "openid.server", HtmlDiscoveryLocalIdKey = "openid.delegate", }; public static readonly Protocol V20 = new Protocol(new QueryParameters() { Realm = "realm", op_endpoint = "op_endpoint", response_nonce = "response_nonce", error_code = "error_code", user_setup_url = null, }) { Version = new Version(2, 0), XmlNamespace = null, // no longer applicable QueryDeclaredNamespaceVersion = Protocol.OpenId2Namespace, ClaimedIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/signon", OPIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/server", ClaimedIdentifierForOPIdentifier = "http://specs.openid.net/auth/2.0/identifier_select", RPReturnToTypeURI = "http://specs.openid.net/auth/2.0/return_to", HtmlDiscoveryProviderKey = "openid2.provider", HtmlDiscoveryLocalIdKey = "openid2.local_id", Args = new QueryArguments() { SessionType = new QueryArguments.SessionTypes() { NoEncryption = "no-encryption", DH_SHA256 = "DH-SHA256", DH_SHA384 = "DH-SHA384", DH_SHA512 = "DH-SHA512", }, SignatureAlgorithm = new QueryArguments.SignatureAlgorithms() { HMAC_SHA256 = "HMAC-SHA256", HMAC_SHA384 = "HMAC-SHA384", HMAC_SHA512 = "HMAC-SHA512", }, Mode = new QueryArguments.Modes() { setup_needed = "setup_needed", }, }, }; /// /// A list of all supported OpenID versions, in order starting from newest version. /// public readonly static List AllVersions = new List() { V20, V11, V10 }; /// /// A list of all supported OpenID versions, in order starting from newest version. /// V1.1 and V1.0 are considered the same and only V1.1 is in the list. /// public readonly static List AllPracticalVersions = new List() { V20, V11 }; /// /// The default (or most recent) supported version of the OpenID protocol. /// public readonly static Protocol Default = AllVersions[0]; public static Protocol Lookup(Version version) { foreach (Protocol protocol in AllVersions) { if (protocol.Version == version) return protocol; } throw new ArgumentOutOfRangeException("version"); } public static Protocol Lookup(ProtocolVersion version) { switch (version) { case ProtocolVersion.V10: return Protocol.V10; case ProtocolVersion.V11: return Protocol.V11; case ProtocolVersion.V20: return Protocol.V20; default: throw new ArgumentOutOfRangeException("version"); } } /// /// Attempts to detect the right OpenID protocol version based on the contents /// of an incoming OpenID indirect message or direct request. /// internal static Protocol Detect(IDictionary query) { Requires.NotNull(query, "query"); return query.ContainsKey(V20.openid.ns) ? V20 : V11; } /// /// Attempts to detect the right OpenID protocol version based on the contents /// of an incoming OpenID direct response message. /// internal static Protocol DetectFromDirectResponse(IDictionary query) { Requires.NotNull(query, "query"); return query.ContainsKey(V20.openidnp.ns) ? V20 : V11; } /// /// Attemps to detect the highest OpenID protocol version supported given a set /// of XRDS Service Type URIs included for some service. /// internal static Protocol Detect(IEnumerable serviceTypeURIs) { Requires.NotNull(serviceTypeURIs, "serviceTypeURIs"); return FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ?? FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs) ?? FindBestVersion(p => p.RPReturnToTypeURI, serviceTypeURIs); } /// /// The OpenID version that this instance describes. /// public Version Version; /// /// Returns the enum value for the instance. /// public ProtocolVersion ProtocolVersion { get { switch (Version.Major) { case 1: return ProtocolVersion.V11; case 2: return ProtocolVersion.V20; default: throw new ArgumentException(null); // this should never happen } } } /// /// The namespace of OpenId 1.x elements in XRDS documents. /// public string XmlNamespace; /// /// The value of the openid.ns parameter that appears on the query string /// whenever data is passed between relying party and provider for OpenID 2.0 /// and later. /// public string QueryDeclaredNamespaceVersion; /// /// The XRD/Service/Type value discovered in an XRDS document when /// "discovering" on a Claimed Identifier (http://andrewarnott.yahoo.com) /// public string ClaimedIdentifierServiceTypeURI; /// /// The XRD/Service/Type value discovered in an XRDS document when /// "discovering" on an OP Identifier rather than a Claimed Identifier. /// (http://yahoo.com) /// public string OPIdentifierServiceTypeURI; /// /// The XRD/Service/Type value discovered in an XRDS document when /// "discovering" on a Realm URL and looking for the endpoint URL /// that can receive authentication assertions. /// public string RPReturnToTypeURI; /// /// Used as the Claimed Identifier and the OP Local Identifier when /// the User Supplied Identifier is an OP Identifier. /// public string ClaimedIdentifierForOPIdentifier; /// /// The value of the 'rel' attribute in an HTML document's LINK tag /// when the same LINK tag's HREF attribute value contains the URL to an /// OP Endpoint URL. /// public string HtmlDiscoveryProviderKey; /// /// The value of the 'rel' attribute in an HTML document's LINK tag /// when the same LINK tag's HREF attribute value contains the URL to use /// as the OP Local Identifier. /// public string HtmlDiscoveryLocalIdKey; /// /// Parts of the protocol that define parameter names that appear in the /// query string. Each parameter name is prefixed with 'openid.'. /// public readonly QueryParameters openid; /// /// Parts of the protocol that define parameter names that appear in the /// query string. Each parameter name is NOT prefixed with 'openid.'. /// public readonly QueryParameters openidnp; /// /// The various 'constants' that appear as parameter arguments (values). /// public QueryArguments Args = new QueryArguments(); internal sealed class QueryParameters { /// /// The value "openid." /// public readonly string Prefix = "openid."; [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] public QueryParameters() { } [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")] public QueryParameters(QueryParameters addPrefixTo) { ns = addPrefix(addPrefixTo.ns); return_to = addPrefix(addPrefixTo.return_to); Realm = addPrefix(addPrefixTo.Realm); mode = addPrefix(addPrefixTo.mode); error = addPrefix(addPrefixTo.error); error_code = addPrefix(addPrefixTo.error_code); identity = addPrefix(addPrefixTo.identity); op_endpoint = addPrefix(addPrefixTo.op_endpoint); response_nonce = addPrefix(addPrefixTo.response_nonce); claimed_id = addPrefix(addPrefixTo.claimed_id); expires_in = addPrefix(addPrefixTo.expires_in); assoc_type = addPrefix(addPrefixTo.assoc_type); assoc_handle = addPrefix(addPrefixTo.assoc_handle); session_type = addPrefix(addPrefixTo.session_type); is_valid = addPrefix(addPrefixTo.is_valid); sig = addPrefix(addPrefixTo.sig); signed = addPrefix(addPrefixTo.signed); user_setup_url = addPrefix(addPrefixTo.user_setup_url); invalidate_handle = addPrefix(addPrefixTo.invalidate_handle); dh_modulus = addPrefix(addPrefixTo.dh_modulus); dh_gen = addPrefix(addPrefixTo.dh_gen); dh_consumer_public = addPrefix(addPrefixTo.dh_consumer_public); dh_server_public = addPrefix(addPrefixTo.dh_server_public); enc_mac_key = addPrefix(addPrefixTo.enc_mac_key); mac_key = addPrefix(addPrefixTo.mac_key); } string addPrefix(string original) { return (original != null) ? Prefix + original : null; } // These fields default to 1.x specifications, and are overridden // as necessary by later versions in the Protocol class initializers. // Null values in any version suggests that that feature is absent from that version. public string ns = "ns"; public string return_to = "return_to"; public string Realm = "trust_root"; public string mode = "mode"; public string error = "error"; public string error_code = null; public string identity = "identity"; public string op_endpoint = null; public string response_nonce = null; public string claimed_id = "claimed_id"; public string expires_in = "expires_in"; public string assoc_type = "assoc_type"; public string assoc_handle = "assoc_handle"; public string session_type = "session_type"; public string is_valid = "is_valid"; public string sig = "sig"; public string signed = "signed"; public string user_setup_url = "user_setup_url"; public string invalidate_handle = "invalidate_handle"; public string dh_modulus = "dh_modulus"; public string dh_gen = "dh_gen"; public string dh_consumer_public = "dh_consumer_public"; public string dh_server_public = "dh_server_public"; public string enc_mac_key = "enc_mac_key"; public string mac_key = "mac_key"; #if CONTRACTS_FULL /// /// Verifies conditions that should be true for any valid state of this object. /// [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(!string.IsNullOrEmpty(this.Prefix)); } #endif } internal sealed class QueryArguments { public ErrorCodes ErrorCode = new ErrorCodes(); public SessionTypes SessionType = new SessionTypes(); public SignatureAlgorithms SignatureAlgorithm = new SignatureAlgorithms(); public Modes Mode = new Modes(); public IsValidValues IsValid = new IsValidValues(); internal sealed class ErrorCodes { public string UnsupportedType = "unsupported-type"; } internal sealed class SessionTypes { /// /// A preference order list of all supported session types. /// public string[] All { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1, NoEncryption }; } } public string[] AllDiffieHellman { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1 }; } } public string DH_SHA1 = "DH-SHA1"; public string DH_SHA256; public string DH_SHA384; public string DH_SHA512; public string NoEncryption = string.Empty; public string Best { get { foreach (string algorithmName in All) { if (algorithmName != null) { return algorithmName; } } throw new ProtocolException(); // really bad... we have no signing algorithms at all } } } internal sealed class SignatureAlgorithms { /// /// A preference order list of signature algorithms we support. /// public string[] All { get { return new[] { HMAC_SHA512, HMAC_SHA384, HMAC_SHA256, HMAC_SHA1 }; } } public string HMAC_SHA1 = "HMAC-SHA1"; public string HMAC_SHA256; public string HMAC_SHA384; public string HMAC_SHA512; public string Best { get { foreach (string algorithmName in All) { if (algorithmName != null) { return algorithmName; } } throw new ProtocolException(); // really bad... we have no signing algorithms at all } } } internal sealed class Modes { public string cancel = "cancel"; public string error = "error"; public string id_res = "id_res"; public string checkid_immediate = "checkid_immediate"; public string checkid_setup = "checkid_setup"; public string check_authentication = "check_authentication"; public string associate = "associate"; public string setup_needed = "id_res"; // V2 overrides this } internal sealed class IsValidValues { public string True = "true"; public string False = "false"; } } /// /// The maximum time a user can be allowed to take to complete authentication. /// /// /// This is used to calculate the length of time that nonces are stored. /// This is internal until we can decide whether to leave this static, or make /// it an instance member, or put it inside the IConsumerApplicationStore interface. /// internal static TimeSpan MaximumUserAgentAuthenticationTime = TimeSpan.FromMinutes(5); /// /// The maximum permissible difference in clocks between relying party and /// provider web servers, discounting time zone differences. /// /// /// This is used when storing/validating nonces from the provider. /// If it is conceivable that a server's clock could be up to five minutes /// off from true UTC time, then the maximum time skew should be set to /// ten minutes to allow one server to be five minutes ahead and the remote /// server to be five minutes behind and still be able to communicate. /// internal static TimeSpan MaximumAllowableTimeSkew = TimeSpan.FromMinutes(10); /// /// Checks whether a given Protocol version practically equals this one /// for purposes of verifying a match for assertion verification. /// /// The other version to check against this one. /// true if this and the given Protocol versions are essentially the same. /// /// OpenID v1.0 never had a spec, and 1.0 and 1.1 are indistinguishable because of that. /// Therefore for assertion verification, 1.0 and 1.1 are considered equivalent. /// public bool EqualsPractically(Protocol other) { if (other == null) { return false; } // Exact version match is definitely equality. if (this.Version == other.Version) { return true; } // If both protocol versions are 1.x, it doesn't matter if one // is 1.0 and the other is 1.1 for assertion verification purposes. if (this.Version.Major == 1 && other.Version.Major == 1) { return true; } // Different version. return false; } public override bool Equals(object obj) { Protocol other = obj as Protocol; if (other == null) { return false; } return this.Version == other.Version; } public override int GetHashCode() { return Version.GetHashCode(); } public override string ToString() { return string.Format(CultureInfo.CurrentCulture, "OpenID Authentication {0}.{1}", Version.Major, Version.Minor); } } }