client.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. 'use strict'
  2. // Load modules
  3. ;
  4. var _typeof = function (obj) {
  5. return obj && typeof Symbol !== 'undefined' && obj.constructor === Symbol ? 'symbol' : typeof obj;
  6. };
  7. var Url = require('url');
  8. var Hoek = require('hoek');
  9. var Cryptiles = require('cryptiles');
  10. var Crypto = require('./crypto');
  11. var Utils = require('./utils');
  12. // Declare internals
  13. var internals = {};
  14. // Generate an Authorization header for a given request
  15. /*
  16. uri: 'http://example.com/resource?a=b' or object from Url.parse()
  17. method: HTTP verb (e.g. 'GET', 'POST')
  18. options: {
  19. // Required
  20. credentials: {
  21. id: 'dh37fgj492je',
  22. key: 'aoijedoaijsdlaksjdl',
  23. algorithm: 'sha256' // 'sha1', 'sha256'
  24. },
  25. // Optional
  26. ext: 'application-specific', // Application specific data sent via the ext attribute
  27. timestamp: Date.now(), // A pre-calculated timestamp
  28. nonce: '2334f34f', // A pre-generated nonce
  29. localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided)
  30. payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided)
  31. contentType: 'application/json', // Payload content-type (ignored if hash provided)
  32. hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash
  33. app: '24s23423f34dx', // Oz application id
  34. dlg: '234sz34tww3sd' // Oz delegated-by application id
  35. }
  36. */
  37. exports.header = function (uri, method, options) {
  38. var result = {
  39. field: '',
  40. artifacts: {}
  41. };
  42. // Validate inputs
  43. if (!uri || typeof uri !== 'string' && (typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) !== 'object' || !method || typeof method !== 'string' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') {
  44. result.err = 'Invalid argument type';
  45. return result;
  46. }
  47. // Application time
  48. var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);
  49. // Validate credentials
  50. var credentials = options.credentials;
  51. if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
  52. result.err = 'Invalid credential object';
  53. return result;
  54. }
  55. if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  56. result.err = 'Unknown algorithm';
  57. return result;
  58. }
  59. // Parse URI
  60. if (typeof uri === 'string') {
  61. uri = Url.parse(uri);
  62. }
  63. // Calculate signature
  64. var artifacts = {
  65. ts: timestamp,
  66. nonce: options.nonce || Cryptiles.randomString(6),
  67. method: method,
  68. resource: uri.pathname + (uri.search || ''), // Maintain trailing '?'
  69. host: uri.hostname,
  70. port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
  71. hash: options.hash,
  72. ext: options.ext,
  73. app: options.app,
  74. dlg: options.dlg
  75. };
  76. result.artifacts = artifacts;
  77. // Calculate payload hash
  78. if (!artifacts.hash && (options.payload || options.payload === '')) {
  79. artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
  80. }
  81. var mac = Crypto.calculateMac('header', credentials, artifacts);
  82. // Construct header
  83. var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''; // Other falsey values allowed
  84. var header = 'Hawk id="' + credentials.id + '", ts="' + artifacts.ts + '", nonce="' + artifacts.nonce + (artifacts.hash ? '", hash="' + artifacts.hash : '') + (hasExt ? '", ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) : '') + '", mac="' + mac + '"';
  85. if (artifacts.app) {
  86. header = header + ', app="' + artifacts.app + (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';
  87. }
  88. result.field = header;
  89. return result;
  90. };
  91. // Validate server response
  92. /*
  93. res: node's response object
  94. artifacts: object received from header().artifacts
  95. options: {
  96. payload: optional payload received
  97. required: specifies if a Server-Authorization header is required. Defaults to 'false'
  98. }
  99. */
  100. exports.authenticate = function (res, credentials, artifacts, options) {
  101. artifacts = Hoek.clone(artifacts);
  102. options = options || {};
  103. if (res.headers['www-authenticate']) {
  104. // Parse HTTP WWW-Authenticate header
  105. var wwwAttributes = Utils.parseAuthorizationHeader(res.headers['www-authenticate'], ['ts', 'tsm', 'error']);
  106. if (wwwAttributes instanceof Error) {
  107. return false;
  108. }
  109. // Validate server timestamp (not used to update clock since it is done via the SNPT client)
  110. if (wwwAttributes.ts) {
  111. var tsm = Crypto.calculateTsMac(wwwAttributes.ts, credentials);
  112. if (tsm !== wwwAttributes.tsm) {
  113. return false;
  114. }
  115. }
  116. }
  117. // Parse HTTP Server-Authorization header
  118. if (!res.headers['server-authorization'] && !options.required) {
  119. return true;
  120. }
  121. var attributes = Utils.parseAuthorizationHeader(res.headers['server-authorization'], ['mac', 'ext', 'hash']);
  122. if (attributes instanceof Error) {
  123. return false;
  124. }
  125. artifacts.ext = attributes.ext;
  126. artifacts.hash = attributes.hash;
  127. var mac = Crypto.calculateMac('response', credentials, artifacts);
  128. if (mac !== attributes.mac) {
  129. return false;
  130. }
  131. if (!options.payload && options.payload !== '') {
  132. return true;
  133. }
  134. if (!attributes.hash) {
  135. return false;
  136. }
  137. var calculatedHash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, res.headers['content-type']);
  138. return calculatedHash === attributes.hash;
  139. };
  140. // Generate a bewit value for a given URI
  141. /*
  142. uri: 'http://example.com/resource?a=b' or object from Url.parse()
  143. options: {
  144. // Required
  145. credentials: {
  146. id: 'dh37fgj492je',
  147. key: 'aoijedoaijsdlaksjdl',
  148. algorithm: 'sha256' // 'sha1', 'sha256'
  149. },
  150. ttlSec: 60 * 60, // TTL in seconds
  151. // Optional
  152. ext: 'application-specific', // Application specific data sent via the ext attribute
  153. localtimeOffsetMsec: 400 // Time offset to sync with server time
  154. };
  155. */
  156. exports.getBewit = function (uri, options) {
  157. // Validate inputs
  158. if (!uri || typeof uri !== 'string' && (typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) !== 'object' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object' || !options.ttlSec) {
  159. return '';
  160. }
  161. options.ext = options.ext === null || options.ext === undefined ? '' : options.ext; // Zero is valid value
  162. // Application time
  163. var now = Utils.now(options.localtimeOffsetMsec);
  164. // Validate credentials
  165. var credentials = options.credentials;
  166. if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
  167. return '';
  168. }
  169. if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  170. return '';
  171. }
  172. // Parse URI
  173. if (typeof uri === 'string') {
  174. uri = Url.parse(uri);
  175. }
  176. // Calculate signature
  177. var exp = Math.floor(now / 1000) + options.ttlSec;
  178. var mac = Crypto.calculateMac('bewit', credentials, {
  179. ts: exp,
  180. nonce: '',
  181. method: 'GET',
  182. resource: uri.pathname + (uri.search || ''), // Maintain trailing '?'
  183. host: uri.hostname,
  184. port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
  185. ext: options.ext
  186. });
  187. // Construct bewit: id\exp\mac\ext
  188. var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext;
  189. return Hoek.base64urlEncode(bewit);
  190. };
  191. // Generate an authorization string for a message
  192. /*
  193. host: 'example.com',
  194. port: 8000,
  195. message: '{"some":"payload"}', // UTF-8 encoded string for body hash generation
  196. options: {
  197. // Required
  198. credentials: {
  199. id: 'dh37fgj492je',
  200. key: 'aoijedoaijsdlaksjdl',
  201. algorithm: 'sha256' // 'sha1', 'sha256'
  202. },
  203. // Optional
  204. timestamp: Date.now(), // A pre-calculated timestamp
  205. nonce: '2334f34f', // A pre-generated nonce
  206. localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided)
  207. }
  208. */
  209. exports.message = function (host, port, message, options) {
  210. // Validate inputs
  211. if (!host || typeof host !== 'string' || !port || typeof port !== 'number' || message === null || message === undefined || typeof message !== 'string' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') {
  212. return null;
  213. }
  214. // Application time
  215. var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);
  216. // Validate credentials
  217. var credentials = options.credentials;
  218. if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
  219. // Invalid credential object
  220. return null;
  221. }
  222. if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  223. return null;
  224. }
  225. // Calculate signature
  226. var artifacts = {
  227. ts: timestamp,
  228. nonce: options.nonce || Cryptiles.randomString(6),
  229. host: host,
  230. port: port,
  231. hash: Crypto.calculatePayloadHash(message, credentials.algorithm)
  232. };
  233. // Construct authorization
  234. var result = {
  235. id: credentials.id,
  236. ts: artifacts.ts,
  237. nonce: artifacts.nonce,
  238. hash: artifacts.hash,
  239. mac: Crypto.calculateMac('message', credentials, artifacts)
  240. };
  241. return result;
  242. };