Name: js-handler/node_modules/restify/node_modules/http-signature/lib/signer.js 
1:
// Copyright 2012 Joyent, Inc.  All rights reserved.
2:
 
3:
var assert = require('assert-plus');
4:
var crypto = require('crypto');
5:
var http = require('http');
6:
 
7:
var sprintf = require('util').format;
8:
 
9:
 
10:
 
11:
///--- Globals
12:
 
13:
var Algorithms = {
14:
  'rsa-sha1': true,
15:
  'rsa-sha256': true,
16:
  'rsa-sha512': true,
17:
  'dsa-sha1': true,
18:
  'hmac-sha1': true,
19:
  'hmac-sha256': true,
20:
  'hmac-sha512': true
21:
};
22:
 
23:
var Authorization =
24:
  'Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"';
25:
 
26:
 
27:
 
28:
///--- Specific Errors
29:
 
30:
function MissingHeaderError(message) {
31:
    this.name = 'MissingHeaderError';
32:
    this.message = message;
33:
    this.stack = (new Error()).stack;
34:
}
35:
MissingHeaderError.prototype = new Error();
36:
 
37:
 
38:
function InvalidAlgorithmError(message) {
39:
    this.name = 'InvalidAlgorithmError';
40:
    this.message = message;
41:
    this.stack = (new Error()).stack;
42:
}
43:
InvalidAlgorithmError.prototype = new Error();
44:
 
45:
 
46:
 
47:
///--- Internal Functions
48:
 
49:
function _pad(val) {
50:
  if (parseInt(val, 10) < 10) {
51:
    val = '0' + val;
52:
  }
53:
  return val;
54:
}
55:
 
56:
 
57:
function _rfc1123() {
58:
  var date = new Date();
59:
 
60:
  var months = ['Jan',
61:
                'Feb',
62:
                'Mar',
63:
                'Apr',
64:
                'May',
65:
                'Jun',
66:
                'Jul',
67:
                'Aug',
68:
                'Sep',
69:
                'Oct',
70:
                'Nov',
71:
                'Dec'];
72:
  var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
73:
  return days[date.getUTCDay()] + ', ' +
74:
    _pad(date.getUTCDate()) + ' ' +
75:
    months[date.getUTCMonth()] + ' ' +
76:
    date.getUTCFullYear() + ' ' +
77:
    _pad(date.getUTCHours()) + ':' +
78:
    _pad(date.getUTCMinutes()) + ':' +
79:
    _pad(date.getUTCSeconds()) +
80:
    ' GMT';
81:
}
82:
 
83:
 
84:
 
85:
///--- Exported API
86:
 
87:
module.exports = {
88:
 
89:
  /**
90:
   * Adds an 'Authorization' header to an http.ClientRequest object.
91:
   *
92:
   * Note that this API will add a Date header if it's not already set. Any
93:
   * other headers in the options.headers array MUST be present, or this
94:
   * will throw.
95:
   *
96:
   * You shouldn't need to check the return type; it's just there if you want
97:
   * to be pedantic.
98:
   *
99:
   * @param {Object} request an instance of http.ClientRequest.
100:
   * @param {Object} options signing parameters object:
101:
   *                   - {String} keyId required.
102:
   *                   - {String} key required (either a PEM or HMAC key).
103:
   *                   - {Array} headers optional; defaults to ['date'].
104:
   *                   - {String} algorithm optional; defaults to 'rsa-sha256'.
105:
   *                   - {String} httpVersion optional; defaults to '1.1'.
106:
   * @return {Boolean} true if Authorization (and optionally Date) were added.
107:
   * @throws {TypeError} on bad parameter types (input).
108:
   * @throws {InvalidAlgorithmError} if algorithm was bad.
109:
   * @throws {MissingHeaderError} if a header to be signed was specified but
110:
   *                              was not present.
111:
   */
112:
  signRequest: function signRequest(request, options) {
113:
    assert.object(request, 'request');
114:
    assert.object(options, 'options');
115:
    assert.optionalString(options.algorithm, 'options.algorithm');
116:
    assert.string(options.keyId, 'options.keyId');
117:
    assert.optionalArrayOfString(options.headers, 'options.headers');
118:
    assert.optionalString(options.httpVersion, 'options.httpVersion');
119:
 
120:
    if (!request.getHeader('Date'))
121:
      request.setHeader('Date', _rfc1123());
122:
    if (!options.headers)
123:
      options.headers = ['date'];
124:
    if (!options.algorithm)
125:
      options.algorithm = 'rsa-sha256';
126:
    if (!options.httpVersion)
127:
      options.httpVersion = '1.1';
128:
 
129:
    options.algorithm = options.algorithm.toLowerCase();
130:
 
131:
    if (!Algorithms[options.algorithm])
132:
      throw new InvalidAlgorithmError(options.algorithm + ' is not supported');
133:
 
134:
    var i;
135:
    var stringToSign = '';
136:
    for (i = 0; i < options.headers.length; i++) {
137:
      if (typeof (options.headers[i]) !== 'string')
138:
        throw new TypeError('options.headers must be an array of Strings');
139:
 
140:
      var h = options.headers[i].toLowerCase();
141:
 
142:
      if (h !== 'request-line') {
143:
        var value = request.getHeader(h);
144:
        if (!value) {
145:
          throw new MissingHeaderError(h + ' was not in the request');
146:
        }
147:
        stringToSign += h + ': ' + value;
148:
      } else {
149:
        value =
150:
        stringToSign +=
151:
          request.method + ' ' + request.path + ' HTTP/' + options.httpVersion;
152:
      }
153:
 
154:
      if ((i + 1) < options.headers.length)
155:
        stringToSign += '\n';
156:
    }
157:
 
158:
    var alg = options.algorithm.match(/(hmac|rsa)-(\w+)/);
159:
    var signature;
160:
    if (alg[1] === 'hmac') {
161:
      var hmac = crypto.createHmac(alg[2].toUpperCase(), options.key);
162:
      hmac.update(stringToSign);
163:
      signature = hmac.digest('base64');
164:
    } else {
165:
      var signer = crypto.createSign(options.algorithm.toUpperCase());
166:
      signer.update(stringToSign);
167:
      signature = signer.sign(options.key, 'base64');
168:
    }
169:
 
170:
    request.setHeader('Authorization', sprintf(Authorization,
171:
                                               options.keyId,
172:
                                               options.algorithm,
173:
                                               options.headers.join(' '),
174:
                                               signature));
175:
 
176:
    return true;
177:
  }
178:
 
179:
};