auth_digest_spec.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. var needle = require('../'),
  2. auth = require('../lib/auth'),
  3. sinon = require('sinon'),
  4. should = require('should'),
  5. http = require('http'),
  6. helpers = require('./helpers');
  7. var createHash = require('crypto').createHash;
  8. function md5(string) {
  9. return createHash('md5').update(string).digest('hex');
  10. }
  11. function parse_header(header) {
  12. var challenge = {},
  13. matches = header.match(/([a-z0-9_-]+)="?([a-z0-9=\/\.@\s-\+]+)"?/gi);
  14. for (var i = 0, l = matches.length; i < l; i++) {
  15. var parts = matches[i].split('='),
  16. key = parts.shift(),
  17. val = parts.join('=').replace(/^"/, '').replace(/"$/, '');
  18. challenge[key] = val;
  19. }
  20. return challenge;
  21. }
  22. describe('auth_digest', function() {
  23. describe('With qop (RFC 2617)', function() {
  24. it('should generate a proper header', function() {
  25. // from https://tools.ietf.org/html/rfc2617
  26. var performDigest = function() {
  27. var header = 'Digest realm="[email protected]", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"';
  28. var user = 'Mufasa';
  29. var pass = 'Circle Of Life';
  30. var method = 'get';
  31. var path = '/dir/index.html';
  32. var updatedHeader = auth.digest(header, user, pass, method, path);
  33. var parsedUpdatedHeader = parse_header(updatedHeader);
  34. var ha1 = md5(user + ':' + parsedUpdatedHeader.realm + ':' + pass);
  35. var ha2 = md5(method.toUpperCase() + ':' + path);
  36. var expectedResponse = md5([
  37. ha1,
  38. parsedUpdatedHeader.nonce,
  39. parsedUpdatedHeader.nc,
  40. parsedUpdatedHeader.cnonce,
  41. parsedUpdatedHeader.qop,
  42. ha2
  43. ].join(':'));
  44. return {
  45. header: updatedHeader,
  46. parsed: parsedUpdatedHeader,
  47. expectedResponse: expectedResponse,
  48. }
  49. }
  50. const result = performDigest();
  51. (result.header).should
  52. .match(/qop="auth"/)
  53. .match(/uri="\/dir\/index.html"/)
  54. .match(/opaque="5ccc069c403ebaf9f0171e9517f40e41"/)
  55. .match(/realm="testrealm@host\.com"/)
  56. .match(/response=/)
  57. .match(/nc=/)
  58. .match(/nonce=/)
  59. .match(/cnonce=/);
  60. (result.parsed.response).should.be.eql(result.expectedResponse);
  61. });
  62. });
  63. describe('With plus character in nonce header', function() {
  64. it('should generate a proper header', function() {
  65. // from https://tools.ietf.org/html/rfc2617
  66. var performDigest = function() {
  67. var header = 'Digest realm="[email protected]", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f6+00bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"';
  68. var user = 'Mufasa';
  69. var pass = 'Circle Of Life';
  70. var method = 'get';
  71. var path = '/dir/index.html';
  72. var updatedHeader = auth.digest(header, user, pass, method, path);
  73. var parsedUpdatedHeader = parse_header(updatedHeader);
  74. var ha1 = md5(user + ':' + parsedUpdatedHeader.realm + ':' + pass);
  75. var ha2 = md5(method.toUpperCase() + ':' + path);
  76. var expectedResponse = md5([
  77. ha1,
  78. parsedUpdatedHeader.nonce,
  79. parsedUpdatedHeader.nc,
  80. parsedUpdatedHeader.cnonce,
  81. parsedUpdatedHeader.qop,
  82. ha2
  83. ].join(':'));
  84. return {
  85. header: updatedHeader,
  86. parsed: parsedUpdatedHeader,
  87. expectedResponse: expectedResponse,
  88. }
  89. }
  90. const result = performDigest();
  91. (result.header).should
  92. .match(/nonce="dcd98b7102dd2f0e8b11d0f6\+00bfb0c093"/)
  93. });
  94. });
  95. describe('With colon character in nonce header', function() {
  96. it('should generate a proper header', function() {
  97. // from https://tools.ietf.org/html/rfc2617
  98. var performDigest = function() {
  99. var header = 'Digest realm="IP Camera", charset="UTF-8", algorithm="MD5", nonce="636144c2:2970b5fdd41b5ac6b669848f43d2d22b", qop="auth"';
  100. var user = 'Mufasa';
  101. var pass = 'Circle Of Life';
  102. var method = 'get';
  103. var path = '/dir/index.html';
  104. var updatedHeader = auth.digest(header, user, pass, method, path);
  105. var parsedUpdatedHeader = parse_header(updatedHeader);
  106. var ha1 = md5(user + ':' + parsedUpdatedHeader.realm + ':' + pass);
  107. var ha2 = md5(method.toUpperCase() + ':' + path);
  108. var expectedResponse = md5([
  109. ha1,
  110. parsedUpdatedHeader.nonce,
  111. parsedUpdatedHeader.nc,
  112. parsedUpdatedHeader.cnonce,
  113. parsedUpdatedHeader.qop,
  114. ha2
  115. ].join(':'));
  116. return {
  117. header: updatedHeader,
  118. parsed: parsedUpdatedHeader,
  119. expectedResponse: expectedResponse,
  120. }
  121. }
  122. const result = performDigest();
  123. (result.header).should
  124. .match(/nonce="636144c2:2970b5fdd41b5ac6b669848f43d2d22b"/)
  125. });
  126. });
  127. describe('With brackets in realm header', function() {
  128. it('should generate a proper header', function() {
  129. // from https://tools.ietf.org/html/rfc2617
  130. var performDigest = function() {
  131. var header = 'Digest qop="auth", realm="IP Camera(76475)", nonce="4e4449794d575269597a706b5a575935595441324d673d3d", stale="FALSE", Basic realm="IP Camera(76475)"';
  132. var user = 'Mufasa';
  133. var pass = 'Circle Of Life';
  134. var method = 'get';
  135. var path = '/dir/index.html';
  136. var updatedHeader = auth.digest(header, user, pass, method, path);
  137. var parsedUpdatedHeader = parse_header(updatedHeader);
  138. var ha1 = md5(user + ':' + parsedUpdatedHeader.realm + ':' + pass);
  139. var ha2 = md5(method.toUpperCase() + ':' + path);
  140. var expectedResponse = md5([
  141. ha1,
  142. parsedUpdatedHeader.nonce,
  143. parsedUpdatedHeader.nc,
  144. parsedUpdatedHeader.cnonce,
  145. parsedUpdatedHeader.qop,
  146. ha2
  147. ].join(':'));
  148. return {
  149. header: updatedHeader,
  150. parsed: parsedUpdatedHeader,
  151. expectedResponse: expectedResponse,
  152. }
  153. }
  154. const result = performDigest();
  155. (result.header).should
  156. .match(/realm="IP Camera\(76475\)"/)
  157. });
  158. });
  159. describe('Without qop (RFC 2617)', function() {
  160. it('should generate a proper header', function() {
  161. // from https://tools.ietf.org/html/rfc2069
  162. var performDigest = function() {
  163. var header = 'Digest realm="[email protected]", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"';
  164. var user = 'Mufasa';
  165. var pass = 'Circle Of Life';
  166. var method = 'get';
  167. var path = '/dir/index.html';
  168. var updatedHeader = auth.digest(header, user, pass, method, path);
  169. var parsedUpdatedHeader = parse_header(updatedHeader);
  170. var ha1 = md5(user + ':' + parsedUpdatedHeader.realm + ':' + pass);
  171. var ha2 = md5(method.toUpperCase() + ':' + path);
  172. var expectedResponse = md5([ha1, parsedUpdatedHeader.nonce, ha2].join(':'));
  173. return {
  174. header: updatedHeader,
  175. parsed: parsedUpdatedHeader,
  176. expectedResponse: expectedResponse,
  177. }
  178. }
  179. const result = performDigest();
  180. (result.header).should
  181. .not.match(/qop=/)
  182. .match(/uri="\/dir\/index.html"/)
  183. .match(/opaque="5ccc069c403ebaf9f0171e9517f40e41"/)
  184. .match(/realm="testrealm@host\.com"/)
  185. .match(/response=/)
  186. .not.match(/nc=/)
  187. .match(/nonce=/)
  188. .not.match(/cnonce=/);
  189. (result.parsed.response).should.be.eql(result.expectedResponse);
  190. });
  191. });
  192. })