AuthenticationResponse.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. #region License
  2. /*
  3. * AuthenticationResponse.cs
  4. *
  5. * ParseBasicCredentials is derived from HttpListenerContext.cs (System.Net) of
  6. * Mono (http://www.mono-project.com).
  7. *
  8. * The MIT License
  9. *
  10. * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
  11. * Copyright (c) 2013-2023 sta.blockhead
  12. *
  13. * Permission is hereby granted, free of charge, to any person obtaining a copy
  14. * of this software and associated documentation files (the "Software"), to deal
  15. * in the Software without restriction, including without limitation the rights
  16. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  17. * copies of the Software, and to permit persons to whom the Software is
  18. * furnished to do so, subject to the following conditions:
  19. *
  20. * The above copyright notice and this permission notice shall be included in
  21. * all copies or substantial portions of the Software.
  22. *
  23. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  28. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  29. * THE SOFTWARE.
  30. */
  31. #endregion
  32. using System;
  33. using System.Collections.Specialized;
  34. using System.Security.Cryptography;
  35. using System.Security.Principal;
  36. using System.Text;
  37. namespace WebSocketSharp.Net
  38. {
  39. internal class AuthenticationResponse
  40. {
  41. #region Private Fields
  42. private uint _nonceCount;
  43. private NameValueCollection _parameters;
  44. private AuthenticationSchemes _scheme;
  45. #endregion
  46. #region Private Constructors
  47. private AuthenticationResponse (
  48. AuthenticationSchemes scheme, NameValueCollection parameters
  49. )
  50. {
  51. _scheme = scheme;
  52. _parameters = parameters;
  53. }
  54. #endregion
  55. #region Internal Constructors
  56. internal AuthenticationResponse (NetworkCredential credentials)
  57. : this (
  58. AuthenticationSchemes.Basic,
  59. new NameValueCollection (),
  60. credentials,
  61. 0
  62. )
  63. {
  64. }
  65. internal AuthenticationResponse (
  66. AuthenticationChallenge challenge,
  67. NetworkCredential credentials,
  68. uint nonceCount
  69. )
  70. : this (challenge.Scheme, challenge.Parameters, credentials, nonceCount)
  71. {
  72. }
  73. internal AuthenticationResponse (
  74. AuthenticationSchemes scheme,
  75. NameValueCollection parameters,
  76. NetworkCredential credentials,
  77. uint nonceCount
  78. )
  79. : this (scheme, parameters)
  80. {
  81. _parameters["username"] = credentials.Username;
  82. _parameters["password"] = credentials.Password;
  83. _parameters["uri"] = credentials.Domain;
  84. _nonceCount = nonceCount;
  85. if (scheme == AuthenticationSchemes.Digest)
  86. initAsDigest ();
  87. }
  88. #endregion
  89. #region Internal Properties
  90. internal uint NonceCount {
  91. get {
  92. return _nonceCount < UInt32.MaxValue
  93. ? _nonceCount
  94. : 0;
  95. }
  96. }
  97. internal NameValueCollection Parameters {
  98. get {
  99. return _parameters;
  100. }
  101. }
  102. #endregion
  103. #region Public Properties
  104. public string Algorithm {
  105. get {
  106. return _parameters["algorithm"];
  107. }
  108. }
  109. public string Cnonce {
  110. get {
  111. return _parameters["cnonce"];
  112. }
  113. }
  114. public string Nc {
  115. get {
  116. return _parameters["nc"];
  117. }
  118. }
  119. public string Nonce {
  120. get {
  121. return _parameters["nonce"];
  122. }
  123. }
  124. public string Opaque {
  125. get {
  126. return _parameters["opaque"];
  127. }
  128. }
  129. public string Password {
  130. get {
  131. return _parameters["password"];
  132. }
  133. }
  134. public string Qop {
  135. get {
  136. return _parameters["qop"];
  137. }
  138. }
  139. public string Realm {
  140. get {
  141. return _parameters["realm"];
  142. }
  143. }
  144. public string Response {
  145. get {
  146. return _parameters["response"];
  147. }
  148. }
  149. public AuthenticationSchemes Scheme {
  150. get {
  151. return _scheme;
  152. }
  153. }
  154. public string Uri {
  155. get {
  156. return _parameters["uri"];
  157. }
  158. }
  159. public string UserName {
  160. get {
  161. return _parameters["username"];
  162. }
  163. }
  164. #endregion
  165. #region Private Methods
  166. private static string createA1 (
  167. string username, string password, string realm
  168. )
  169. {
  170. return String.Format ("{0}:{1}:{2}", username, realm, password);
  171. }
  172. private static string createA1 (
  173. string username,
  174. string password,
  175. string realm,
  176. string nonce,
  177. string cnonce
  178. )
  179. {
  180. var a1 = createA1 (username, password, realm);
  181. return String.Format ("{0}:{1}:{2}", hash (a1), nonce, cnonce);
  182. }
  183. private static string createA2 (string method, string uri)
  184. {
  185. return String.Format ("{0}:{1}", method, uri);
  186. }
  187. private static string createA2 (string method, string uri, string entity)
  188. {
  189. return String.Format ("{0}:{1}:{2}", method, uri, hash (entity));
  190. }
  191. private static string hash (string value)
  192. {
  193. var md5 = MD5.Create ();
  194. var bytes = Encoding.UTF8.GetBytes (value);
  195. var res = md5.ComputeHash (bytes);
  196. var buff = new StringBuilder (64);
  197. foreach (var b in res)
  198. buff.Append (b.ToString ("x2"));
  199. return buff.ToString ();
  200. }
  201. private void initAsDigest ()
  202. {
  203. var qops = _parameters["qop"];
  204. if (qops != null) {
  205. var auth = qops.Split (',').Contains (
  206. qop => qop.Trim ().ToLower () == "auth"
  207. );
  208. if (auth) {
  209. _parameters["qop"] = "auth";
  210. _parameters["cnonce"] = AuthenticationChallenge.CreateNonceValue ();
  211. _parameters["nc"] = String.Format ("{0:x8}", ++_nonceCount);
  212. }
  213. else {
  214. _parameters["qop"] = null;
  215. }
  216. }
  217. _parameters["method"] = "GET";
  218. _parameters["response"] = CreateRequestDigest (_parameters);
  219. }
  220. #endregion
  221. #region Internal Methods
  222. internal static string CreateRequestDigest (NameValueCollection parameters)
  223. {
  224. var user = parameters["username"];
  225. var pass = parameters["password"];
  226. var realm = parameters["realm"];
  227. var nonce = parameters["nonce"];
  228. var uri = parameters["uri"];
  229. var algo = parameters["algorithm"];
  230. var qop = parameters["qop"];
  231. var cnonce = parameters["cnonce"];
  232. var nc = parameters["nc"];
  233. var method = parameters["method"];
  234. var a1 = algo != null && algo.ToLower () == "md5-sess"
  235. ? createA1 (user, pass, realm, nonce, cnonce)
  236. : createA1 (user, pass, realm);
  237. var a2 = qop != null && qop.ToLower () == "auth-int"
  238. ? createA2 (method, uri, parameters["entity"])
  239. : createA2 (method, uri);
  240. var secret = hash (a1);
  241. var data = qop != null
  242. ? String.Format (
  243. "{0}:{1}:{2}:{3}:{4}", nonce, nc, cnonce, qop, hash (a2)
  244. )
  245. : String.Format ("{0}:{1}", nonce, hash (a2));
  246. var keyed = String.Format ("{0}:{1}", secret, data);
  247. return hash (keyed);
  248. }
  249. internal static AuthenticationResponse Parse (string value)
  250. {
  251. try {
  252. var cred = value.Split (new[] { ' ' }, 2);
  253. if (cred.Length != 2)
  254. return null;
  255. var schm = cred[0].ToLower ();
  256. if (schm == "basic") {
  257. var parameters = ParseBasicCredentials (cred[1]);
  258. return new AuthenticationResponse (
  259. AuthenticationSchemes.Basic, parameters
  260. );
  261. }
  262. if (schm == "digest") {
  263. var parameters = AuthenticationChallenge.ParseParameters (cred[1]);
  264. return new AuthenticationResponse (
  265. AuthenticationSchemes.Digest, parameters
  266. );
  267. }
  268. return null;
  269. }
  270. catch {
  271. return null;
  272. }
  273. }
  274. internal static NameValueCollection ParseBasicCredentials (string value)
  275. {
  276. var ret = new NameValueCollection ();
  277. // Decode the basic-credentials (a Base64 encoded string).
  278. var bytes = Convert.FromBase64String (value);
  279. var userPass = Encoding.Default.GetString (bytes);
  280. // The format is [<domain>\]<username>:<password>.
  281. var i = userPass.IndexOf (':');
  282. var user = userPass.Substring (0, i);
  283. var pass = i < userPass.Length - 1
  284. ? userPass.Substring (i + 1)
  285. : String.Empty;
  286. // Check if <domain> exists.
  287. i = user.IndexOf ('\\');
  288. if (i > -1)
  289. user = user.Substring (i + 1);
  290. ret["username"] = user;
  291. ret["password"] = pass;
  292. return ret;
  293. }
  294. internal string ToBasicString ()
  295. {
  296. var user = _parameters["username"];
  297. var pass = _parameters["password"];
  298. var userPass = String.Format ("{0}:{1}", user, pass);
  299. var bytes = Encoding.UTF8.GetBytes (userPass);
  300. var cred = Convert.ToBase64String (bytes);
  301. return "Basic " + cred;
  302. }
  303. internal string ToDigestString ()
  304. {
  305. var buff = new StringBuilder (256);
  306. var user = _parameters["username"];
  307. var realm = _parameters["realm"];
  308. var nonce = _parameters["nonce"];
  309. var uri = _parameters["uri"];
  310. var res = _parameters["response"];
  311. buff.AppendFormat (
  312. "Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", response=\"{4}\"",
  313. user,
  314. realm,
  315. nonce,
  316. uri,
  317. res
  318. );
  319. var opaque = _parameters["opaque"];
  320. if (opaque != null)
  321. buff.AppendFormat (", opaque=\"{0}\"", opaque);
  322. var algo = _parameters["algorithm"];
  323. if (algo != null)
  324. buff.AppendFormat (", algorithm={0}", algo);
  325. var qop = _parameters["qop"];
  326. if (qop != null) {
  327. var cnonce = _parameters["cnonce"];
  328. var nc = _parameters["nc"];
  329. buff.AppendFormat (
  330. ", qop={0}, cnonce=\"{1}\", nc={2}", qop, cnonce, nc
  331. );
  332. }
  333. return buff.ToString ();
  334. }
  335. #endregion
  336. #region Public Methods
  337. public IIdentity ToIdentity ()
  338. {
  339. if (_scheme == AuthenticationSchemes.Basic) {
  340. var user = _parameters["username"];
  341. var pass = _parameters["password"];
  342. return new HttpBasicIdentity (user, pass);
  343. }
  344. if (_scheme == AuthenticationSchemes.Digest)
  345. return new HttpDigestIdentity (_parameters);
  346. return null;
  347. }
  348. public override string ToString ()
  349. {
  350. if (_scheme == AuthenticationSchemes.Basic)
  351. return ToBasicString ();
  352. if (_scheme == AuthenticationSchemes.Digest)
  353. return ToDigestString ();
  354. return String.Empty;
  355. }
  356. #endregion
  357. }
  358. }