PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/WP7.1/Libraries/DPE.OAuth/Tokens/SimpleWebTokenHandler.cs

#
C# | 468 lines | 374 code | 76 blank | 18 comment | 52 complexity | 4cc032529e7a2e18458829cb6778240e 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(
  193. CultureInfo.InvariantCulture,
  194. "Symmetric key was not found for the key identifier clause: Keys='{0}', Values='{1}'",
  195. string.Join(",", keyIdentifierClause.Dictionary.Keys.ToArray()),
  196. string.Join(",", keyIdentifierClause.Dictionary.Values.ToArray())));
  197. }
  198. if (!this.IsValidSignature(accessToken, securityKey.GetSymmetricKey()))
  199. {
  200. throw new SecurityTokenValidationException("Signature is invalid");
  201. }
  202. if (this.IsExpired(accessToken))
  203. {
  204. throw new SecurityTokenExpirationException(
  205. string.Format("The token is expired",
  206. (DateTime.UtcNow - accessToken.ValidTo).TotalSeconds));
  207. }
  208. string issuerName;
  209. if (!this.IsIssuerTrusted(accessToken, out issuerName))
  210. {
  211. throw new SecurityTokenException(string.Format("The Issuer {0} is not trusted", accessToken.Issuer));
  212. }
  213. if (!this.IsAudienceTrusted(accessToken))
  214. {
  215. throw new SecurityTokenException(string.Format("The audience {0} of the token is not trusted", accessToken.Audience));
  216. }
  217. var identity = this.CreateClaimsIdentity(accessToken.Parameters, issuerName);
  218. return new ClaimsIdentityCollection(new IClaimsIdentity[] { identity });
  219. }
  220. protected static ulong GetExpiresOn(TimeSpan secondsFromNow)
  221. {
  222. DateTime expiresDate = DateTime.UtcNow + secondsFromNow;
  223. TimeSpan ts = expiresDate - new DateTime(1970, 1, 1, 0, 0, 0, 0);
  224. return Convert.ToUInt64(ts.TotalSeconds);
  225. }
  226. protected static string GenerateSignature(string unsignedToken, byte[] signingKey)
  227. {
  228. using (HMACSHA256 hmac = new HMACSHA256(signingKey))
  229. {
  230. byte[] signatureBytes = hmac.ComputeHash(Encoding.ASCII.GetBytes(unsignedToken));
  231. string signature = HttpUtility.UrlEncode(Convert.ToBase64String(signatureBytes));
  232. return signature;
  233. }
  234. }
  235. protected static string SerializeToken(SimpleWebToken swt, SecurityTokenResolver tokenResolver)
  236. {
  237. StringBuilder builder = new StringBuilder(64);
  238. builder.Append("Id=");
  239. builder.Append(swt.Id);
  240. builder.Append('&');
  241. builder.Append(IssuerLabel);
  242. builder.Append('=');
  243. builder.Append(swt.Issuer);
  244. if (swt.Parameters.Count > 0)
  245. {
  246. builder.Append('&');
  247. foreach (string key in swt.Parameters.AllKeys)
  248. {
  249. builder.Append(key);
  250. builder.Append('=');
  251. builder.Append(swt.Parameters[key]);
  252. builder.Append('&');
  253. }
  254. }
  255. else
  256. {
  257. builder.Append('&');
  258. }
  259. builder.Append(ExpiresOnLabel);
  260. builder.Append('=');
  261. builder.Append(GetExpiresOn(swt.TokenValidity));
  262. if (!string.IsNullOrEmpty(swt.Audience))
  263. {
  264. builder.Append('&');
  265. builder.Append(AudienceLabel);
  266. builder.Append('=');
  267. builder.Append(swt.Audience);
  268. }
  269. builder.Append('&');
  270. builder.Append(SignatureAlgorithmLabel);
  271. builder.Append('=');
  272. builder.Append(SignatureAlgorithm);
  273. var keyIdentifierClause = new DictionaryBasedKeyIdentifierClause(ToDictionary(swt));
  274. InMemorySymmetricSecurityKey securityKey;
  275. try
  276. {
  277. securityKey = (InMemorySymmetricSecurityKey)tokenResolver.ResolveSecurityKey(keyIdentifierClause);
  278. }
  279. catch (InvalidOperationException)
  280. {
  281. 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())));
  282. }
  283. string signature = GenerateSignature(builder.ToString(), securityKey.GetSymmetricKey());
  284. builder.Append("&" + SignatureLabel + "=");
  285. builder.Append(signature);
  286. return builder.ToString();
  287. }
  288. protected bool IsAudienceTrusted(SimpleWebToken accessToken)
  289. {
  290. if (this.AudienceRestriction.AudienceMode == AudienceUriMode.Never)
  291. {
  292. return true;
  293. }
  294. if (!string.IsNullOrEmpty(accessToken.Audience))
  295. {
  296. return this.AudienceRestriction.AllowedAudienceUris.Contains(new Uri(accessToken.Audience));
  297. }
  298. return false;
  299. }
  300. protected bool IsExpired(SimpleWebToken accessToken)
  301. {
  302. if (accessToken.ValidTo > DateTime.UtcNow)
  303. {
  304. return false;
  305. }
  306. return true;
  307. }
  308. protected IClaimsIdentity CreateClaimsIdentity(NameValueCollection values, string issuer)
  309. {
  310. var identity = new ClaimsIdentity("SimpleWebToken");
  311. foreach (var key in values.AllKeys)
  312. {
  313. Uri claimType;
  314. bool validUri = Uri.TryCreate(key, UriKind.Absolute, out claimType);
  315. if (!validUri)
  316. {
  317. claimType = new Uri(String.Format(Namespace, key));
  318. }
  319. var multivalues = values[key].Split(',');
  320. foreach (var value in multivalues)
  321. {
  322. identity.Claims.Add(
  323. new Claim(
  324. claimType.ToString(),
  325. value,
  326. ClaimValueTypes.String,
  327. issuer));
  328. }
  329. }
  330. return identity;
  331. }
  332. protected bool IsValidSignature(SimpleWebToken token, byte[] signingKey)
  333. {
  334. var unsignedToken = this.GetUnsignedToken(token.RawToken);
  335. if (string.IsNullOrEmpty(unsignedToken))
  336. {
  337. return false;
  338. }
  339. var localSignature = GenerateSignature(unsignedToken, signingKey);
  340. var incomingSignature = HttpUtility.UrlEncode(Convert.ToBase64String(token.Signature));
  341. return localSignature.Equals(incomingSignature, StringComparison.Ordinal);
  342. }
  343. private static string ExtractSignature(string swtSigned)
  344. {
  345. var parts = swtSigned.Split(new string[] { string.Format("&{0}=", SignatureLabel) }, StringSplitOptions.None);
  346. var signature = Uri.UnescapeDataString(parts[1]);
  347. return signature;
  348. }
  349. private static IDictionary<string, string> ToDictionary(SimpleWebToken token)
  350. {
  351. var dictionary = new Dictionary<string, string>
  352. {
  353. { "Issuer", token.Issuer },
  354. { "Audience", token.Audience }
  355. };
  356. return dictionary;
  357. }
  358. private DateTime GetDateTimeFromExpiresOn(ulong seconds)
  359. {
  360. var start = new DateTime(1970, 1, 1, 0, 0, 0, 0);
  361. var expiresOn = start.AddSeconds(seconds);
  362. return expiresOn;
  363. }
  364. private bool IsIssuerTrusted(SimpleWebToken accessToken, out string issuerName)
  365. {
  366. issuerName = null;
  367. if (!string.IsNullOrEmpty(accessToken.Issuer))
  368. {
  369. if (this.IssuerNameRegistry != null)
  370. {
  371. issuerName = this.IssuerNameRegistry.GetIssuerName(accessToken);
  372. if (!string.IsNullOrEmpty(issuerName))
  373. {
  374. return true;
  375. }
  376. }
  377. }
  378. return false;
  379. }
  380. private string GetUnsignedToken(string swtSigned)
  381. {
  382. // Assume signature is at the end of token
  383. var signaturePosition = swtSigned.IndexOf(string.Format("&{0}=", SignatureLabel));
  384. if (signaturePosition <= 0)
  385. {
  386. return null;
  387. }
  388. var unsigned = swtSigned.Substring(0, signaturePosition);
  389. return unsigned;
  390. }
  391. }
  392. }