index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*!
  2. * knox - auth
  3. * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var crypto = require('crypto')
  10. , parse = require('url').parse
  11. ;
  12. /**
  13. * Valid keys.
  14. */
  15. var keys =
  16. [ 'acl'
  17. , 'location'
  18. , 'logging'
  19. , 'notification'
  20. , 'partNumber'
  21. , 'policy'
  22. , 'requestPayment'
  23. , 'torrent'
  24. , 'uploadId'
  25. , 'uploads'
  26. , 'versionId'
  27. , 'versioning'
  28. , 'versions'
  29. , 'website'
  30. ]
  31. /**
  32. * Return an "Authorization" header value with the given `options`
  33. * in the form of "AWS <key>:<signature>"
  34. *
  35. * @param {Object} options
  36. * @return {String}
  37. * @api private
  38. */
  39. function authorization (options) {
  40. return 'AWS ' + options.key + ':' + sign(options)
  41. }
  42. module.exports = authorization
  43. module.exports.authorization = authorization
  44. /**
  45. * Simple HMAC-SHA1 Wrapper
  46. *
  47. * @param {Object} options
  48. * @return {String}
  49. * @api private
  50. */
  51. function hmacSha1 (options) {
  52. return crypto.createHmac('sha1', options.secret).update(options.message).digest('base64')
  53. }
  54. module.exports.hmacSha1 = hmacSha1
  55. /**
  56. * Create a base64 sha1 HMAC for `options`.
  57. *
  58. * @param {Object} options
  59. * @return {String}
  60. * @api private
  61. */
  62. function sign (options) {
  63. options.message = stringToSign(options)
  64. return hmacSha1(options)
  65. }
  66. module.exports.sign = sign
  67. /**
  68. * Create a base64 sha1 HMAC for `options`.
  69. *
  70. * Specifically to be used with S3 presigned URLs
  71. *
  72. * @param {Object} options
  73. * @return {String}
  74. * @api private
  75. */
  76. function signQuery (options) {
  77. options.message = queryStringToSign(options)
  78. return hmacSha1(options)
  79. }
  80. module.exports.signQuery= signQuery
  81. /**
  82. * Return a string for sign() with the given `options`.
  83. *
  84. * Spec:
  85. *
  86. * <verb>\n
  87. * <md5>\n
  88. * <content-type>\n
  89. * <date>\n
  90. * [headers\n]
  91. * <resource>
  92. *
  93. * @param {Object} options
  94. * @return {String}
  95. * @api private
  96. */
  97. function stringToSign (options) {
  98. var headers = options.amazonHeaders || ''
  99. if (headers) headers += '\n'
  100. var r =
  101. [ options.verb
  102. , options.md5
  103. , options.contentType
  104. , options.date ? options.date.toUTCString() : ''
  105. , headers + options.resource
  106. ]
  107. return r.join('\n')
  108. }
  109. module.exports.queryStringToSign = stringToSign
  110. /**
  111. * Return a string for sign() with the given `options`, but is meant exclusively
  112. * for S3 presigned URLs
  113. *
  114. * Spec:
  115. *
  116. * <date>\n
  117. * <resource>
  118. *
  119. * @param {Object} options
  120. * @return {String}
  121. * @api private
  122. */
  123. function queryStringToSign (options){
  124. return 'GET\n\n\n' + options.date + '\n' + options.resource
  125. }
  126. module.exports.queryStringToSign = queryStringToSign
  127. /**
  128. * Perform the following:
  129. *
  130. * - ignore non-amazon headers
  131. * - lowercase fields
  132. * - sort lexicographically
  133. * - trim whitespace between ":"
  134. * - join with newline
  135. *
  136. * @param {Object} headers
  137. * @return {String}
  138. * @api private
  139. */
  140. function canonicalizeHeaders (headers) {
  141. var buf = []
  142. , fields = Object.keys(headers)
  143. ;
  144. for (var i = 0, len = fields.length; i < len; ++i) {
  145. var field = fields[i]
  146. , val = headers[field]
  147. , field = field.toLowerCase()
  148. ;
  149. if (0 !== field.indexOf('x-amz')) continue
  150. buf.push(field + ':' + val)
  151. }
  152. return buf.sort().join('\n')
  153. }
  154. module.exports.canonicalizeHeaders = canonicalizeHeaders
  155. /**
  156. * Perform the following:
  157. *
  158. * - ignore non sub-resources
  159. * - sort lexicographically
  160. *
  161. * @param {String} resource
  162. * @return {String}
  163. * @api private
  164. */
  165. function canonicalizeResource (resource) {
  166. var url = parse(resource, true)
  167. , path = url.pathname
  168. , buf = []
  169. ;
  170. Object.keys(url.query).forEach(function(key){
  171. if (!~keys.indexOf(key)) return
  172. var val = '' == url.query[key] ? '' : '=' + encodeURIComponent(url.query[key])
  173. buf.push(key + val)
  174. })
  175. return path + (buf.length ? '?' + buf.sort().join('&') : '')
  176. }
  177. module.exports.canonicalizeResource = canonicalizeResource