FxCop fixes.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / HmacShaAssociation.cs
blobb959d1af1b8dc7ede7a34ebe7a223cb4a740863f
1 //-----------------------------------------------------------------------
2 // <copyright file="HmacShaAssociation.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.OpenId {
8 using System;
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.Linq;
12 using System.Security.Cryptography;
13 using DotNetOpenAuth.Messaging;
14 using DotNetOpenAuth.OpenId;
15 using DotNetOpenAuth.OpenId.Messages;
16 using DotNetOpenAuth.OpenId.Provider;
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 /// The default lifetime of a shared association when no lifetime is given
24 /// for a specific association type.
25 /// </summary>
26 private static readonly TimeSpan DefaultMaximumLifetime = TimeSpan.FromDays(14);
28 /// <summary>
29 /// A list of HMAC-SHA algorithms in order of decreasing bit lengths.
30 /// </summary>
31 private static HmacSha[] hmacShaAssociationTypes = {
32 new HmacSha {
33 CreateHasher = secretKey => new HMACSHA512(secretKey),
34 GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA512,
35 BaseHashAlgorithm = new SHA512Managed(),
37 new HmacSha {
38 CreateHasher = secretKey => new HMACSHA384(secretKey),
39 GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA384,
40 BaseHashAlgorithm = new SHA384Managed(),
42 new HmacSha {
43 CreateHasher = secretKey => new HMACSHA256(secretKey),
44 GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA256,
45 BaseHashAlgorithm = new SHA256Managed(),
47 new HmacSha {
48 CreateHasher = secretKey => new HMACSHA1(secretKey),
49 GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA1,
50 BaseHashAlgorithm = new SHA1Managed(),
54 /// <summary>
55 /// The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)
56 /// </summary>
57 private HmacSha typeIdentity;
59 /// <summary>
60 /// Initializes a new instance of the <see cref="HmacShaAssociation"/> class.
61 /// </summary>
62 /// <param name="typeIdentity">The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)</param>
63 /// <param name="handle">The association handle.</param>
64 /// <param name="secret">The association secret.</param>
65 /// <param name="totalLifeLength">The time duration the association will be good for.</param>
66 private HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength)
67 : base(handle, secret, totalLifeLength, DateTime.UtcNow) {
68 ErrorUtilities.VerifyArgumentNotNull(typeIdentity, "typeIdentity");
69 ErrorUtilities.VerifyNonZeroLength(handle, "handle");
70 ErrorUtilities.VerifyArgumentNotNull(secret, "secret");
71 ErrorUtilities.VerifyProtocol(secret.Length == typeIdentity.SecretLength, OpenIdStrings.AssociationSecretAndTypeLengthMismatch, secret.Length, typeIdentity.GetAssociationType(Protocol.Default));
73 this.typeIdentity = typeIdentity;
76 /// <summary>
77 /// Creates an HMAC-SHA association.
78 /// </summary>
79 /// <param name="protocol">The OpenID protocol version that the request for an association came in on.</param>
80 /// <param name="associationType">The value of the openid.assoc_type parameter.</param>
81 /// <param name="handle">The association handle.</param>
82 /// <param name="secret">The association secret.</param>
83 /// <param name="totalLifeLength">How long the association will be good for.</param>
84 /// <returns>The newly created association.</returns>
85 public static HmacShaAssociation Create(Protocol protocol, string associationType, string handle, byte[] secret, TimeSpan totalLifeLength) {
86 ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol");
87 ErrorUtilities.VerifyNonZeroLength(associationType, "associationType");
88 ErrorUtilities.VerifyArgumentNotNull(secret, "secret");
90 HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => String.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal));
91 ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType);
92 return new HmacShaAssociation(match, handle, secret, totalLifeLength);
95 /// <summary>
96 /// Creates an association with the specified handle, secret, and lifetime.
97 /// </summary>
98 /// <param name="handle">The handle.</param>
99 /// <param name="secret">The secret.</param>
100 /// <param name="totalLifeLength">Total lifetime.</param>
101 /// <returns>The newly created association.</returns>
102 public static HmacShaAssociation Create(string handle, byte[] secret, TimeSpan totalLifeLength) {
103 ErrorUtilities.VerifyNonZeroLength(handle, "handle");
104 ErrorUtilities.VerifyArgumentNotNull(secret, "secret");
106 HmacSha shaType = hmacShaAssociationTypes.FirstOrDefault(sha => sha.SecretLength == secret.Length);
107 ErrorUtilities.VerifyProtocol(shaType != null, OpenIdStrings.NoAssociationTypeFoundByLength, secret.Length);
108 return new HmacShaAssociation(shaType, handle, secret, totalLifeLength);
111 /// <summary>
112 /// Returns the length of the shared secret (in bytes).
113 /// </summary>
114 /// <param name="protocol">The protocol version being used that will be used to lookup the text in <paramref name="associationType"/></param>
115 /// <param name="associationType">The value of the protocol argument specifying the type of association. For example: "HMAC-SHA1".</param>
116 /// <returns>The length (in bytes) of the association secret.</returns>
117 /// <exception cref="ProtocolException">Thrown if no association can be found by the given name.</exception>
118 public static int GetSecretLength(Protocol protocol, string associationType) {
119 HmacSha match = hmacShaAssociationTypes.FirstOrDefault(shaType => String.Equals(shaType.GetAssociationType(protocol), associationType, StringComparison.Ordinal));
120 ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType);
121 return match.SecretLength;
124 /// <summary>
125 /// Creates a new association of a given type.
126 /// </summary>
127 /// <param name="protocol">The protocol.</param>
128 /// <param name="associationType">Type of the association.</param>
129 /// <param name="associationUse">A value indicating whether the new association will be used privately by the Provider for "dumb mode" authentication
130 /// or shared with the Relying Party for "smart mode" authentication.</param>
131 /// <param name="securitySettings">The security settings of the Provider.</param>
132 /// <returns>The newly created association.</returns>
133 /// <remarks>
134 /// The new association is NOT automatically put into an association store. This must be done by the caller.
135 /// </remarks>
136 internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, ProviderSecuritySettings securitySettings) {
137 ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol");
138 ErrorUtilities.VerifyNonZeroLength(associationType, "associationType");
139 ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings");
141 // Generate the handle. It must be unique, so we use a time element and a random data element to generate it.
142 string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4);
143 string handle = "{" + associationType + "}{" + DateTime.UtcNow.Ticks + "}{" + uniq + "}";
145 // Generate the secret that will be used for signing
146 int secretLength = GetSecretLength(protocol, associationType);
147 byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength);
149 TimeSpan lifetime;
150 if (associationUse == AssociationRelyingPartyType.Smart) {
151 if (!securitySettings.AssociationLifetimes.TryGetValue(associationType, out lifetime)) {
152 lifetime = DefaultMaximumLifetime;
154 } else {
155 lifetime = DumbSecretLifetime;
158 return Create(protocol, associationType, handle, secret, lifetime);
161 /// <summary>
162 /// Looks for the first association type in a preferred-order list that is
163 /// likely to be supported given a specific OpenID version and the security settings,
164 /// and perhaps a matching Diffie-Hellman session type.
165 /// </summary>
166 /// <param name="protocol">The OpenID version that dictates which associations are available.</param>
167 /// <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>
168 /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param>
169 /// <param name="requireMatchingDHSessionType">Use <c>true</c> for HTTP associations, <c>false</c> for HTTPS associations.</param>
170 /// <param name="associationType">The resulting association type's well known protocol name. (i.e. HMAC-SHA256)</param>
171 /// <param name="sessionType">The resulting session type's well known protocol name, if a matching one is available. (i.e. DH-SHA256)</param>
172 /// <returns>
173 /// True if a qualifying association could be found; false otherwise.
174 /// </returns>
175 internal static bool TryFindBestAssociation(Protocol protocol, bool highSecurityIsBetter, SecuritySettings securityRequirements, bool requireMatchingDHSessionType, out string associationType, out string sessionType) {
176 ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol");
177 ErrorUtilities.VerifyArgumentNotNull(securityRequirements, "securityRequirements");
179 associationType = null;
180 sessionType = null;
182 // We use AsEnumerable() to avoid VerificationException (http://stackoverflow.com/questions/478422/why-does-simple-array-and-linq-generate-verificationexception-operation-could-de)
183 IEnumerable<HmacSha> preferredOrder = highSecurityIsBetter ?
184 hmacShaAssociationTypes.AsEnumerable() : hmacShaAssociationTypes.Reverse();
186 foreach (HmacSha sha in preferredOrder) {
187 int hashSizeInBits = sha.SecretLength * 8;
188 if (hashSizeInBits > securityRequirements.MaximumHashBitLength ||
189 hashSizeInBits < securityRequirements.MinimumHashBitLength) {
190 continue;
192 sessionType = DiffieHellmanUtilities.GetNameForSize(protocol, hashSizeInBits);
193 if (requireMatchingDHSessionType && sessionType == null) {
194 continue;
196 associationType = sha.GetAssociationType(protocol);
197 if (associationType == null) {
198 continue;
201 return true;
204 return false;
207 /// <summary>
208 /// Determines whether a named Diffie-Hellman session type and association type can be used together.
209 /// </summary>
210 /// <param name="protocol">The protocol carrying the names of the session and association types.</param>
211 /// <param name="associationType">The value of the openid.assoc_type parameter.</param>
212 /// <param name="sessionType">The value of the openid.session_type parameter.</param>
213 /// <returns>
214 /// <c>true</c> if the named association and session types are compatible; otherwise, <c>false</c>.
215 /// </returns>
216 internal static bool IsDHSessionCompatible(Protocol protocol, string associationType, string sessionType) {
217 ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol");
218 ErrorUtilities.VerifyNonZeroLength(associationType, "associationType");
219 ErrorUtilities.VerifyArgumentNotNull(sessionType, "sessionType");
221 // All association types can work when no DH session is used at all.
222 if (string.Equals(sessionType, protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal)) {
223 return true;
226 // When there _is_ a DH session, it must match in hash length with the association type.
227 int associationSecretLengthInBytes = GetSecretLength(protocol, associationType);
228 int sessionHashLengthInBytes = DiffieHellmanUtilities.Lookup(protocol, sessionType).HashSize / 8;
229 return associationSecretLengthInBytes == sessionHashLengthInBytes;
232 /// <summary>
233 /// Gets the string to pass as the assoc_type value in the OpenID protocol.
234 /// </summary>
235 /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param>
236 /// <returns>
237 /// The value that should be used for the openid.assoc_type parameter.
238 /// </returns>
239 internal override string GetAssociationType(Protocol protocol) {
240 return this.typeIdentity.GetAssociationType(protocol);
243 /// <summary>
244 /// Returns the specific hash algorithm used for message signing.
245 /// </summary>
246 /// <returns>
247 /// The hash algorithm used for message signing.
248 /// </returns>
249 protected override HashAlgorithm CreateHasher() {
250 return this.typeIdentity.CreateHasher(SecretKey);
253 /// <summary>
254 /// Provides information about some HMAC-SHA hashing algorithm that OpenID supports.
255 /// </summary>
256 private class HmacSha {
257 /// <summary>
258 /// Gets or sets the function that takes a particular OpenID version and returns the value of the openid.assoc_type parameter in that protocol.
259 /// </summary>
260 internal Func<Protocol, string> GetAssociationType { get; set; }
262 /// <summary>
263 /// Gets or sets a function that will create the <see cref="HashAlgorithm"/> using a given shared secret for the mac.
264 /// </summary>
265 internal Func<byte[], HashAlgorithm> CreateHasher { get; set; }
267 /// <summary>
268 /// Gets or sets the base hash algorithm.
269 /// </summary>
270 internal HashAlgorithm BaseHashAlgorithm { get; set; }
272 /// <summary>
273 /// Gets the size of the hash (in bytes).
274 /// </summary>
275 internal int SecretLength { get { return this.BaseHashAlgorithm.HashSize / 8; } }