/csharp/DuoWeb/DuoWeb.cs

https://github.com/jpellerin/duo_web · C# · 127 lines · 83 code · 20 blank · 24 comment · 9 complexity · 9d1355311dac922fd0e90485671815c8 MD5 · raw file

  1. /*
  2. * DuoWeb.cs
  3. *
  4. * Copyright (c) 2011 Duo Security
  5. * All rights reserved, all wrongs reversed.
  6. */
  7. using System;
  8. using System.IO;
  9. using System.Net;
  10. using System.Web;
  11. using System.Text;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. using System.Security.Cryptography;
  15. namespace Duo
  16. {
  17. public static class Web
  18. {
  19. const string REQUEST_PREFIX = "TX";
  20. const int REQUEST_EXPIRE = 300;
  21. const string RESPONSE_PREFIX = "AUTH";
  22. /// <summary>
  23. /// Generate a signed request for the Duo IFRAME.
  24. /// The returned sig_request should be passed into the Duo.init() call
  25. /// in the rendered web page used for secondary authentication.
  26. /// </summary>
  27. /// <param name="skey">Duo secret key</param>
  28. /// <param name="ikey">Duo integration key</param>
  29. /// <param name="username">Primary-authenticated username</param>
  30. /// <returns>signed request</returns>
  31. public static string SignRequest(string skey, string ikey, string username)
  32. {
  33. int ts = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
  34. int expire_ts = ts + REQUEST_EXPIRE;
  35. string expire = expire_ts.ToString();
  36. string val = username + "|" + ikey + "|" + expire;
  37. string cookie = REQUEST_PREFIX + "|" + Encode64(val);
  38. string sig = HmacSign(skey, cookie);
  39. return cookie + "|" + sig;
  40. }
  41. /// <summary>
  42. /// Validate the signed response returned from the Duo IFRAME.
  43. /// Returns the username of the successfully-authenticated user
  44. /// (which should be verified against the original username passed
  45. /// to SignRequest()), or null.
  46. /// </summary>
  47. /// <param name="skey">Duo secret key</param>
  48. /// <param name="sig_response">The signed response POST'ed to the server</param>
  49. /// <returns>The username of the authenticated user, or null</returns>
  50. public static string VerifyResponse(string skey, string sig_response)
  51. {
  52. try {
  53. int ts = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
  54. string[] parts = sig_response.Split('|');
  55. if (parts.Length != 3) {
  56. return null;
  57. }
  58. string u_prefix = parts[0];
  59. string u_b64 = parts[1];
  60. string u_sig = parts[2];
  61. string sig = HmacSign(skey, u_prefix + "|" + u_b64);
  62. if (HmacSign(skey, sig) != HmacSign(skey, u_sig)) {
  63. return null;
  64. }
  65. if (u_prefix != RESPONSE_PREFIX) {
  66. return null;
  67. }
  68. string val = Decode64(u_b64);
  69. string[] cookie_parts = val.Split('|');
  70. if (cookie_parts.Length != 3) {
  71. return null;
  72. }
  73. string username = cookie_parts[0];
  74. string ikey = cookie_parts[1];
  75. string expire = cookie_parts[2];
  76. int expire_ts = Convert.ToInt32(expire);
  77. if (ts >= expire_ts) {
  78. return null;
  79. }
  80. return username;
  81. } catch {
  82. return null;
  83. }
  84. }
  85. private static string HmacSign(string skey, string data)
  86. {
  87. byte[] key_bytes = ASCIIEncoding.ASCII.GetBytes(skey);
  88. HMACSHA1 hmac = new HMACSHA1(key_bytes);
  89. byte[] data_bytes = ASCIIEncoding.ASCII.GetBytes(data);
  90. hmac.ComputeHash(data_bytes);
  91. string hex = BitConverter.ToString(hmac.Hash);
  92. return hex.Replace("-", "").ToLower();
  93. }
  94. private static string Encode64(string plaintext)
  95. {
  96. byte[] plaintext_bytes = ASCIIEncoding.ASCII.GetBytes(plaintext);
  97. string encoded = System.Convert.ToBase64String(plaintext_bytes);
  98. return encoded;
  99. }
  100. private static string Decode64(string encoded)
  101. {
  102. byte[] plaintext_bytes = System.Convert.FromBase64String(encoded);
  103. string plaintext = ASCIIEncoding.ASCII.GetString(plaintext_bytes);
  104. return plaintext;
  105. }
  106. }
  107. }