PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs

http://github.com/AArnott/dotnetopenid
C# | 282 lines | 139 code | 29 blank | 114 comment | 18 complexity | 56cd29eae4bda952ca3bfb25f455ad42 MD5 | raw file
  1. //-----------------------------------------------------------------------
  2. // <copyright file="HmacShaAssociation.cs" company="Outercurve Foundation">
  3. // Copyright (c) Outercurve Foundation. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. namespace DotNetOpenAuth.OpenId {
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Diagnostics;
  10. using System.Diagnostics.Contracts;
  11. using System.Globalization;
  12. using System.Linq;
  13. using System.Security.Cryptography;
  14. using DotNetOpenAuth.Messaging;
  15. using DotNetOpenAuth.OpenId;
  16. using DotNetOpenAuth.OpenId.Messages;
  17. using Validation;
  18. /// <summary>
  19. /// An association that uses the HMAC-SHA family of algorithms for message signing.
  20. /// </summary>
  21. internal class HmacShaAssociation : Association {
  22. /// <summary>
  23. /// A list of HMAC-SHA algorithms in order of decreasing bit lengths.
  24. /// </summary>
  25. private static HmacSha[] hmacShaAssociationTypes = CreateAssociationTypes();
  26. /// <summary>
  27. /// The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)
  28. /// </summary>
  29. private HmacSha typeIdentity;
  30. /// <summary>
  31. /// Initializes a new instance of the <see cref="HmacShaAssociation"/> class.
  32. /// </summary>
  33. /// <param name="typeIdentity">The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)</param>
  34. /// <param name="handle">The association handle.</param>
  35. /// <param name="secret">The association secret.</param>
  36. /// <param name="totalLifeLength">The time duration the association will be good for.</param>
  37. private HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength)
  38. : base(handle, secret, totalLifeLength, DateTime.UtcNow) {
  39. Requires.NotNull(typeIdentity, "typeIdentity");
  40. Requires.NotNullOrEmpty(handle, "handle");
  41. Requires.NotNull(secret, "secret");
  42. Requires.Range(totalLifeLength > TimeSpan.Zero, "totalLifeLength");
  43. ErrorUtilities.VerifyProtocol(secret.Length == typeIdentity.SecretLength, OpenIdStrings.AssociationSecretAndTypeLengthMismatch, secret.Length, typeIdentity.GetAssociationType(Protocol.Default));
  44. this.typeIdentity = typeIdentity;
  45. }
  46. /// <summary>
  47. /// Gets the length (in bits) of the hash this association creates when signing.
  48. /// </summary>
  49. public override int HashBitLength {
  50. get {
  51. Protocol protocol = Protocol.Default;
  52. return HmacShaAssociation.GetSecretLength(protocol, this.GetAssociationType(protocol)) * 8;
  53. }
  54. }
  55. /// <summary>
  56. /// Creates an HMAC-SHA association.
  57. /// </summary>
  58. /// <param name="protocol">The OpenID protocol version that the request for an association came in on.</param>
  59. /// <param name="associationType">The value of the openid.assoc_type parameter.</param>
  60. /// <param name="handle">The association handle.</param>
  61. /// <param name="secret">The association secret.</param>
  62. /// <param name="totalLifeLength">How long the association will be good for.</param>
  63. /// <returns>The newly created association.</returns>
  64. public static HmacShaAssociation Create(Protocol protocol, string associationType, string handle, byte[] secret, TimeSpan totalLifeLength) {
  65. Requires.NotNull(protocol, "protocol");
  66. Requires.NotNullOrEmpty(associationType, "associationType");
  67. Requires.NotNull(secret, "secret");
  68. HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => string.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal));
  69. ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType);
  70. return new HmacShaAssociation(match, handle, secret, totalLifeLength);
  71. }
  72. /// <summary>
  73. /// Creates an association with the specified handle, secret, and lifetime.
  74. /// </summary>
  75. /// <param name="handle">The handle.</param>
  76. /// <param name="secret">The secret.</param>
  77. /// <param name="totalLifeLength">Total lifetime.</param>
  78. /// <returns>The newly created association.</returns>
  79. public static HmacShaAssociation Create(string handle, byte[] secret, TimeSpan totalLifeLength) {
  80. Requires.NotNullOrEmpty(handle, "handle");
  81. Requires.NotNull(secret, "secret");
  82. HmacSha shaType = hmacShaAssociationTypes.FirstOrDefault(sha => sha.SecretLength == secret.Length);
  83. ErrorUtilities.VerifyProtocol(shaType != null, OpenIdStrings.NoAssociationTypeFoundByLength, secret.Length);
  84. return new HmacShaAssociation(shaType, handle, secret, totalLifeLength);
  85. }
  86. /// <summary>
  87. /// Returns the length of the shared secret (in bytes).
  88. /// </summary>
  89. /// <param name="protocol">The protocol version being used that will be used to lookup the text in <paramref name="associationType"/></param>
  90. /// <param name="associationType">The value of the protocol argument specifying the type of association. For example: "HMAC-SHA1".</param>
  91. /// <returns>The length (in bytes) of the association secret.</returns>
  92. /// <exception cref="ProtocolException">Thrown if no association can be found by the given name.</exception>
  93. public static int GetSecretLength(Protocol protocol, string associationType) {
  94. HmacSha match = hmacShaAssociationTypes.FirstOrDefault(shaType => string.Equals(shaType.GetAssociationType(protocol), associationType, StringComparison.Ordinal));
  95. ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType);
  96. return match.SecretLength;
  97. }
  98. /// <summary>
  99. /// Looks for the first association type in a preferred-order list that is
  100. /// likely to be supported given a specific OpenID version and the security settings,
  101. /// and perhaps a matching Diffie-Hellman session type.
  102. /// </summary>
  103. /// <param name="protocol">The OpenID version that dictates which associations are available.</param>
  104. /// <param name="highSecurityIsBetter">A value indicating whether to consider higher strength security to be better. Use <c>true</c> for initial association requests from the Relying Party; use <c>false</c> from Providers when the Relying Party asks for an unrecognized association in order to pick a suggested alternative that is likely to be supported on both sides.</param>
  105. /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param>
  106. /// <param name="requireMatchingDHSessionType">Use <c>true</c> for HTTP associations, <c>false</c> for HTTPS associations.</param>
  107. /// <param name="associationType">The resulting association type's well known protocol name. (i.e. HMAC-SHA256)</param>
  108. /// <param name="sessionType">The resulting session type's well known protocol name, if a matching one is available. (i.e. DH-SHA256)</param>
  109. /// <returns>
  110. /// True if a qualifying association could be found; false otherwise.
  111. /// </returns>
  112. internal static bool TryFindBestAssociation(Protocol protocol, bool highSecurityIsBetter, SecuritySettings securityRequirements, bool requireMatchingDHSessionType, out string associationType, out string sessionType) {
  113. Requires.NotNull(protocol, "protocol");
  114. Requires.NotNull(securityRequirements, "securityRequirements");
  115. associationType = null;
  116. sessionType = null;
  117. // We use AsEnumerable() to avoid VerificationException (http://stackoverflow.com/questions/478422/why-does-simple-array-and-linq-generate-verificationexception-operation-could-de)
  118. IEnumerable<HmacSha> preferredOrder = highSecurityIsBetter ?
  119. hmacShaAssociationTypes.AsEnumerable() : hmacShaAssociationTypes.Reverse();
  120. foreach (HmacSha sha in preferredOrder) {
  121. int hashSizeInBits = sha.SecretLength * 8;
  122. if (hashSizeInBits > securityRequirements.MaximumHashBitLength ||
  123. hashSizeInBits < securityRequirements.MinimumHashBitLength) {
  124. continue;
  125. }
  126. if (OpenIdUtilities.IsDiffieHellmanPresent) {
  127. sessionType = DiffieHellmanUtilities.GetNameForSize(protocol, hashSizeInBits);
  128. } else {
  129. sessionType = requireMatchingDHSessionType ? null : protocol.Args.SessionType.NoEncryption;
  130. }
  131. if (requireMatchingDHSessionType && sessionType == null) {
  132. continue;
  133. }
  134. associationType = sha.GetAssociationType(protocol);
  135. if (associationType == null) {
  136. continue;
  137. }
  138. return true;
  139. }
  140. return false;
  141. }
  142. /// <summary>
  143. /// Determines whether a named Diffie-Hellman session type and association type can be used together.
  144. /// </summary>
  145. /// <param name="protocol">The protocol carrying the names of the session and association types.</param>
  146. /// <param name="associationType">The value of the openid.assoc_type parameter.</param>
  147. /// <param name="sessionType">The value of the openid.session_type parameter.</param>
  148. /// <returns>
  149. /// <c>true</c> if the named association and session types are compatible; otherwise, <c>false</c>.
  150. /// </returns>
  151. internal static bool IsDHSessionCompatible(Protocol protocol, string associationType, string sessionType) {
  152. Requires.NotNull(protocol, "protocol");
  153. Requires.NotNullOrEmpty(associationType, "associationType");
  154. Requires.NotNull(sessionType, "sessionType");
  155. // All association types can work when no DH session is used at all.
  156. if (string.Equals(sessionType, protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal)) {
  157. return true;
  158. }
  159. if (OpenIdUtilities.IsDiffieHellmanPresent) {
  160. // When there _is_ a DH session, it must match in hash length with the association type.
  161. int associationSecretLengthInBytes = GetSecretLength(protocol, associationType);
  162. int sessionHashLengthInBytes = DiffieHellmanUtilities.Lookup(protocol, sessionType).HashSize / 8;
  163. return associationSecretLengthInBytes == sessionHashLengthInBytes;
  164. } else {
  165. return false;
  166. }
  167. }
  168. /// <summary>
  169. /// Gets the string to pass as the assoc_type value in the OpenID protocol.
  170. /// </summary>
  171. /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param>
  172. /// <returns>
  173. /// The value that should be used for the openid.assoc_type parameter.
  174. /// </returns>
  175. [Pure]
  176. internal override string GetAssociationType(Protocol protocol) {
  177. return this.typeIdentity.GetAssociationType(protocol);
  178. }
  179. /// <summary>
  180. /// Returns the specific hash algorithm used for message signing.
  181. /// </summary>
  182. /// <returns>
  183. /// The hash algorithm used for message signing.
  184. /// </returns>
  185. [Pure]
  186. protected override HashAlgorithm CreateHasher() {
  187. var result = this.typeIdentity.CreateHasher(SecretKey);
  188. Assumes.True(result != null);
  189. return result;
  190. }
  191. /// <summary>
  192. /// Returns the value used to initialize the static field storing association types.
  193. /// </summary>
  194. /// <returns>A non-null, non-empty array.</returns>
  195. /// <remarks>>
  196. /// This is a method rather than being inlined to the field initializer to try to avoid
  197. /// the CLR bug that crops up sometimes if we initialize arrays using object initializer syntax.
  198. /// </remarks>
  199. private static HmacSha[] CreateAssociationTypes() {
  200. return new[] {
  201. new HmacSha {
  202. HmacAlgorithmName = HmacAlgorithms.HmacSha384,
  203. GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA512,
  204. BaseHashAlgorithm = SHA512.Create(),
  205. },
  206. new HmacSha {
  207. HmacAlgorithmName = HmacAlgorithms.HmacSha384,
  208. GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA384,
  209. BaseHashAlgorithm = SHA384.Create(),
  210. },
  211. new HmacSha {
  212. HmacAlgorithmName = HmacAlgorithms.HmacSha256,
  213. GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA256,
  214. BaseHashAlgorithm = SHA256.Create(),
  215. },
  216. new HmacSha {
  217. HmacAlgorithmName = HmacAlgorithms.HmacSha1,
  218. GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA1,
  219. BaseHashAlgorithm = SHA1.Create(),
  220. },
  221. };
  222. }
  223. /// <summary>
  224. /// Provides information about some HMAC-SHA hashing algorithm that OpenID supports.
  225. /// </summary>
  226. private class HmacSha {
  227. /// <summary>
  228. /// Gets or sets the function that takes a particular OpenID version and returns the value of the openid.assoc_type parameter in that protocol.
  229. /// </summary>
  230. internal Func<Protocol, string> GetAssociationType { get; set; }
  231. /// <summary>
  232. /// Gets or sets the name of the HMAC-SHA algorithm. (e.g. "HMAC-SHA256")
  233. /// </summary>
  234. internal string HmacAlgorithmName { get; set; }
  235. /// <summary>
  236. /// Gets or sets the base hash algorithm.
  237. /// </summary>
  238. internal HashAlgorithm BaseHashAlgorithm { get; set; }
  239. /// <summary>
  240. /// Gets the size of the hash (in bytes).
  241. /// </summary>
  242. internal int SecretLength { get { return this.BaseHashAlgorithm.HashSize / 8; } }
  243. /// <summary>
  244. /// Creates the <see cref="HashAlgorithm"/> using a given shared secret for the mac.
  245. /// </summary>
  246. /// <param name="secret">The HMAC secret.</param>
  247. /// <returns>The algorithm.</returns>
  248. internal HashAlgorithm CreateHasher(byte[] secret) {
  249. return HmacAlgorithms.Create(this.HmacAlgorithmName, secret);
  250. }
  251. }
  252. }
  253. }