PageRenderTime 37ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/WebServices/Acs2WindowsPhoneSample/DPE.OAuth/Tokens/SimpleWebTokenHandler.cs

#
C# | 462 lines | 368 code | 76 blank | 18 comment | 52 complexity | 2da127a26e53f40636f213ddbe17efc1 MD5 | raw file
  1. // ----------------------------------------------------------------------------------
  2. // Microsoft Developer & Platform Evangelism
  3. //
  4. // Copyright (c) Microsoft Corporation. All rights reserved.
  5. //
  6. // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
  7. // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
  8. // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
  9. // ----------------------------------------------------------------------------------
  10. // The example companies, organizations, products, domain names,
  11. // e-mail addresses, logos, people, places, and events depicted
  12. // herein are fictitious. No association with any real company,
  13. // organization, product, domain name, email address, logo, person,
  14. // places, or events is intended or should be inferred.
  15. // ----------------------------------------------------------------------------------
  16. namespace Microsoft.Samples.DPE.OAuth.Tokens
  17. {
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Collections.Specialized;
  21. using System.Globalization;
  22. using System.IdentityModel.Selectors;
  23. using System.IdentityModel.Tokens;
  24. using System.Linq;
  25. using System.Security.Cryptography;
  26. using System.Text;
  27. using System.Web;
  28. using Microsoft.IdentityModel.Claims;
  29. using Microsoft.IdentityModel.Tokens;
  30. using Microsoft.Samples.DPE.OAuth.ProtectedResource;
  31. public class SimpleWebTokenHandler : StringTokenHandler
  32. {
  33. public const string Namespace = "http://simple.web.token/{0}";
  34. public const string IssuerLabel = "Issuer";
  35. public const string SignatureLabel = "HMACSHA256";
  36. public const string AudienceLabel = "Audience";
  37. public const string ExpiresOnLabel = "ExpiresOn";
  38. public const string SignatureAlgorithmLabel = "signatureAlgorithm";
  39. public const string SignatureAlgorithm = "HMAC";
  40. public const string IdLabel = "Id";
  41. private SecurityTokenResolver securityTokenResolver;
  42. private IssuerNameRegistry issuerNameRegistry;
  43. private AudienceRestriction audienceRestriction;
  44. public SimpleWebTokenHandler()
  45. {
  46. }
  47. public SecurityTokenResolver SecurityTokenResolver
  48. {
  49. get
  50. {
  51. if (this.securityTokenResolver == null && this.Configuration != null && this.Configuration.IssuerTokenResolver != null)
  52. {
  53. this.securityTokenResolver = this.Configuration.IssuerTokenResolver;
  54. }
  55. return this.securityTokenResolver;
  56. }
  57. set
  58. {
  59. this.securityTokenResolver = value;
  60. }
  61. }
  62. public IssuerNameRegistry IssuerNameRegistry
  63. {
  64. get
  65. {
  66. if (this.issuerNameRegistry == null && this.Configuration != null && this.Configuration.IssuerNameRegistry != null)
  67. {
  68. this.issuerNameRegistry = this.Configuration.IssuerNameRegistry;
  69. }
  70. return this.issuerNameRegistry;
  71. }
  72. set
  73. {
  74. this.issuerNameRegistry = value;
  75. }
  76. }
  77. public AudienceRestriction AudienceRestriction
  78. {
  79. get
  80. {
  81. if (this.audienceRestriction == null && this.Configuration != null && this.Configuration.AudienceRestriction != null)
  82. {
  83. this.audienceRestriction = this.Configuration.AudienceRestriction;
  84. }
  85. return this.audienceRestriction;
  86. }
  87. set
  88. {
  89. this.audienceRestriction = value;
  90. }
  91. }
  92. public override Type TokenType
  93. {
  94. get
  95. {
  96. return typeof(SimpleWebToken);
  97. }
  98. }
  99. public override bool CanValidateToken
  100. {
  101. get { return true; }
  102. }
  103. public override string GetTokenAsString(SecurityToken token)
  104. {
  105. SimpleWebToken swt = token as SimpleWebToken;
  106. if (swt == null)
  107. {
  108. throw new Exception("Incorrect token type. Expected SimpleWebToken");
  109. }
  110. if (this.SecurityTokenResolver == null)
  111. {
  112. throw new InvalidOperationException("SecurityTokenResolver is not configured");
  113. }
  114. var swtSigned = SerializeToken(swt, this.SecurityTokenResolver);
  115. return swtSigned;
  116. }
  117. public override SecurityToken GetTokenFromString(string token)
  118. {
  119. // TODO: validate
  120. var items = HttpUtility.ParseQueryString(token);
  121. var issuer = items[IssuerLabel];
  122. items.Remove(IssuerLabel);
  123. var audience = items[AudienceLabel];
  124. items.Remove(AudienceLabel);
  125. var expiresOn = items[ExpiresOnLabel];
  126. items.Remove(ExpiresOnLabel);
  127. var id = items[IdLabel];
  128. items.Remove(IdLabel);
  129. var algorithm = items[SignatureAlgorithmLabel];
  130. items.Remove(SignatureAlgorithmLabel);
  131. // Treat signature differently to avoid loosing characters like '+' in the decoding
  132. var signature = ExtractSignature(HttpUtility.UrlDecode(token));
  133. items.Remove(SignatureLabel);
  134. byte[] signatureBytes = Convert.FromBase64String(signature);
  135. DateTime validTo = this.GetDateTimeFromExpiresOn((ulong)Convert.ToInt64(expiresOn));
  136. var swt = new SimpleWebToken(issuer)
  137. {
  138. Audience = audience,
  139. Signature = signatureBytes,
  140. TokenValidity = validTo - DateTime.UtcNow
  141. };
  142. if (id != null)
  143. {
  144. swt.SetId(id);
  145. }
  146. if (string.IsNullOrEmpty(algorithm))
  147. {
  148. swt.SignatureAlgorithm = algorithm;
  149. }
  150. foreach (string key in items.AllKeys)
  151. {
  152. swt.AddClaim(key, items[key]);
  153. }
  154. swt.RawToken = token;
  155. return swt;
  156. }
  157. public override string[] GetTokenTypeIdentifiers()
  158. {
  159. return new string[] { "http://schemas.microsoft.com/2009/11/identitymodel/tokens/swt" };
  160. }
  161. public override ClaimsIdentityCollection ValidateToken(SecurityToken token)
  162. {
  163. if (token == null)
  164. {
  165. throw new ArgumentNullException("token is null");
  166. }
  167. if (this.SecurityTokenResolver == null)
  168. {
  169. throw new InvalidOperationException("SecurityTokenResolver is not configured");
  170. }
  171. if (this.IssuerNameRegistry == null)
  172. {
  173. throw new InvalidOperationException("IssuerNameRegistry is not configured");
  174. }
  175. if (this.AudienceRestriction == null)
  176. {
  177. throw new InvalidOperationException("AudienceRestriction is not configured");
  178. }
  179. SimpleWebToken accessToken = token as SimpleWebToken;
  180. if (accessToken == null)
  181. {
  182. throw new ArgumentNullException("This handler expects a SimpleWebToken");
  183. }
  184. var keyIdentifierClause = new DictionaryBasedKeyIdentifierClause(ToDictionary(accessToken));
  185. InMemorySymmetricSecurityKey securityKey;
  186. try
  187. {
  188. securityKey = (InMemorySymmetricSecurityKey)this.SecurityTokenResolver.ResolveSecurityKey(keyIdentifierClause);
  189. }
  190. catch (InvalidOperationException)
  191. {
  192. throw new SecurityTokenValidationException(string.Format(CultureInfo.InvariantCulture, "Simmetryc key was not found for the key identifier clause: Keys='{0}', Values='{1}'", string.Join(",", keyIdentifierClause.Dictionary.Keys.ToArray()), string.Join(",", keyIdentifierClause.Dictionary.Values.ToArray())));
  193. }
  194. if (!this.IsValidSignature(accessToken, securityKey.GetSymmetricKey()))
  195. {
  196. throw new SecurityTokenValidationException("Signature is invalid");
  197. }
  198. if (this.IsExpired(accessToken))
  199. {
  200. throw new SecurityTokenException(string.Format("Token has been expired for {0} seconds already", (DateTime.UtcNow - accessToken.ValidTo).TotalSeconds));
  201. }
  202. string issuerName;
  203. if (!this.IsIssuerTrusted(accessToken, out issuerName))
  204. {
  205. throw new SecurityTokenException(string.Format("The Issuer {0} is not trusted", accessToken.Issuer));
  206. }
  207. if (!this.IsAudienceTrusted(accessToken))
  208. {
  209. throw new SecurityTokenException(string.Format("The audience {0} of the token is not trusted", accessToken.Audience));
  210. }
  211. var identity = this.CreateClaimsIdentity(accessToken.Parameters, issuerName);
  212. return new ClaimsIdentityCollection(new IClaimsIdentity[] { identity });
  213. }
  214. protected static ulong GetExpiresOn(TimeSpan secondsFromNow)
  215. {
  216. DateTime expiresDate = DateTime.UtcNow + secondsFromNow;
  217. TimeSpan ts = expiresDate - new DateTime(1970, 1, 1, 0, 0, 0, 0);
  218. return Convert.ToUInt64(ts.TotalSeconds);
  219. }
  220. protected static string GenerateSignature(string unsignedToken, byte[] signingKey)
  221. {
  222. using (HMACSHA256 hmac = new HMACSHA256(signingKey))
  223. {
  224. byte[] signatureBytes = hmac.ComputeHash(Encoding.ASCII.GetBytes(unsignedToken));
  225. string signature = HttpUtility.UrlEncode(Convert.ToBase64String(signatureBytes));
  226. return signature;
  227. }
  228. }
  229. protected static string SerializeToken(SimpleWebToken swt, SecurityTokenResolver tokenResolver)
  230. {
  231. StringBuilder builder = new StringBuilder(64);
  232. builder.Append("Id=");
  233. builder.Append(swt.Id);
  234. builder.Append('&');
  235. builder.Append(IssuerLabel);
  236. builder.Append('=');
  237. builder.Append(swt.Issuer);
  238. if (swt.Parameters.Count > 0)
  239. {
  240. builder.Append('&');
  241. foreach (string key in swt.Parameters.AllKeys)
  242. {
  243. builder.Append(key);
  244. builder.Append('=');
  245. builder.Append(swt.Parameters[key]);
  246. builder.Append('&');
  247. }
  248. }
  249. else
  250. {
  251. builder.Append('&');
  252. }
  253. builder.Append(ExpiresOnLabel);
  254. builder.Append('=');
  255. builder.Append(GetExpiresOn(swt.TokenValidity));
  256. if (!string.IsNullOrEmpty(swt.Audience))
  257. {
  258. builder.Append('&');
  259. builder.Append(AudienceLabel);
  260. builder.Append('=');
  261. builder.Append(swt.Audience);
  262. }
  263. builder.Append('&');
  264. builder.Append(SignatureAlgorithmLabel);
  265. builder.Append('=');
  266. builder.Append(SignatureAlgorithm);
  267. var keyIdentifierClause = new DictionaryBasedKeyIdentifierClause(ToDictionary(swt));
  268. InMemorySymmetricSecurityKey securityKey;
  269. try
  270. {
  271. securityKey = (InMemorySymmetricSecurityKey)tokenResolver.ResolveSecurityKey(keyIdentifierClause);
  272. }
  273. catch (InvalidOperationException)
  274. {
  275. throw new SecurityTokenValidationException(string.Format(CultureInfo.InvariantCulture, "Simmetryc key was not found for the key identifier clause: Keys='{0}', Values='{1}'", string.Join(",", keyIdentifierClause.Dictionary.Keys.ToArray()), string.Join(",", keyIdentifierClause.Dictionary.Values.ToArray())));
  276. }
  277. string signature = GenerateSignature(builder.ToString(), securityKey.GetSymmetricKey());
  278. builder.Append("&" + SignatureLabel + "=");
  279. builder.Append(signature);
  280. return builder.ToString();
  281. }
  282. protected bool IsAudienceTrusted(SimpleWebToken accessToken)
  283. {
  284. if (this.AudienceRestriction.AudienceMode == AudienceUriMode.Never)
  285. {
  286. return true;
  287. }
  288. if (!string.IsNullOrEmpty(accessToken.Audience))
  289. {
  290. return this.AudienceRestriction.AllowedAudienceUris.Contains(new Uri(accessToken.Audience));
  291. }
  292. return false;
  293. }
  294. protected bool IsExpired(SimpleWebToken accessToken)
  295. {
  296. if (accessToken.ValidTo > DateTime.UtcNow)
  297. {
  298. return false;
  299. }
  300. return true;
  301. }
  302. protected IClaimsIdentity CreateClaimsIdentity(NameValueCollection values, string issuer)
  303. {
  304. var identity = new ClaimsIdentity("SimpleWebToken");
  305. foreach (var key in values.AllKeys)
  306. {
  307. Uri claimType;
  308. bool validUri = Uri.TryCreate(key, UriKind.Absolute, out claimType);
  309. if (!validUri)
  310. {
  311. claimType = new Uri(String.Format(Namespace, key));
  312. }
  313. var multivalues = values[key].Split(',');
  314. foreach (var value in multivalues)
  315. {
  316. identity.Claims.Add(
  317. new Claim(
  318. claimType.ToString(),
  319. value,
  320. ClaimValueTypes.String,
  321. issuer));
  322. }
  323. }
  324. return identity;
  325. }
  326. protected bool IsValidSignature(SimpleWebToken token, byte[] signingKey)
  327. {
  328. var unsignedToken = this.GetUnsignedToken(token.RawToken);
  329. if (string.IsNullOrEmpty(unsignedToken))
  330. {
  331. return false;
  332. }
  333. var localSignature = GenerateSignature(unsignedToken, signingKey);
  334. var incomingSignature = HttpUtility.UrlEncode(Convert.ToBase64String(token.Signature));
  335. return localSignature.Equals(incomingSignature, StringComparison.Ordinal);
  336. }
  337. private static string ExtractSignature(string swtSigned)
  338. {
  339. var parts = swtSigned.Split(new string[] { string.Format("&{0}=", SignatureLabel) }, StringSplitOptions.None);
  340. var signature = Uri.UnescapeDataString(parts[1]);
  341. return signature;
  342. }
  343. private static IDictionary<string, string> ToDictionary(SimpleWebToken token)
  344. {
  345. var dictionary = new Dictionary<string, string>
  346. {
  347. { "Issuer", token.Issuer },
  348. { "Audience", token.Audience }
  349. };
  350. return dictionary;
  351. }
  352. private DateTime GetDateTimeFromExpiresOn(ulong seconds)
  353. {
  354. var start = new DateTime(1970, 1, 1, 0, 0, 0, 0);
  355. var expiresOn = start.AddSeconds(seconds);
  356. return expiresOn;
  357. }
  358. private bool IsIssuerTrusted(SimpleWebToken accessToken, out string issuerName)
  359. {
  360. issuerName = null;
  361. if (!string.IsNullOrEmpty(accessToken.Issuer))
  362. {
  363. if (this.IssuerNameRegistry != null)
  364. {
  365. issuerName = this.IssuerNameRegistry.GetIssuerName(accessToken);
  366. if (!string.IsNullOrEmpty(issuerName))
  367. {
  368. return true;
  369. }
  370. }
  371. }
  372. return false;
  373. }
  374. private string GetUnsignedToken(string swtSigned)
  375. {
  376. // Assume signature is at the end of token
  377. var signaturePosition = swtSigned.IndexOf(string.Format("&{0}=", SignatureLabel));
  378. if (signaturePosition <= 0)
  379. {
  380. return null;
  381. }
  382. var unsigned = swtSigned.Substring(0, signaturePosition);
  383. return unsigned;
  384. }
  385. }
  386. }