UNPKG

6.73 kBJavaScriptView Raw
1var AWS = require('../core');
2var v4Credentials = require('./v4_credentials');
3var inherit = AWS.util.inherit;
4
5/**
6 * @api private
7 */
8var expiresHeader = 'presigned-expires';
9
10/**
11 * @api private
12 */
13AWS.Signers.V4 = inherit(AWS.Signers.RequestSigner, {
14 constructor: function V4(request, serviceName, options) {
15 AWS.Signers.RequestSigner.call(this, request);
16 this.serviceName = serviceName;
17 options = options || {};
18 this.signatureCache = typeof options.signatureCache === 'boolean' ? options.signatureCache : true;
19 this.operation = options.operation;
20 this.signatureVersion = options.signatureVersion;
21 },
22
23 algorithm: 'AWS4-HMAC-SHA256',
24
25 addAuthorization: function addAuthorization(credentials, date) {
26 var datetime = AWS.util.date.iso8601(date).replace(/[:\-]|\.\d{3}/g, '');
27
28 if (this.isPresigned()) {
29 this.updateForPresigned(credentials, datetime);
30 } else {
31 this.addHeaders(credentials, datetime);
32 }
33
34 this.request.headers['Authorization'] =
35 this.authorization(credentials, datetime);
36 },
37
38 addHeaders: function addHeaders(credentials, datetime) {
39 this.request.headers['X-Amz-Date'] = datetime;
40 if (credentials.sessionToken) {
41 this.request.headers['x-amz-security-token'] = credentials.sessionToken;
42 }
43 },
44
45 updateForPresigned: function updateForPresigned(credentials, datetime) {
46 var credString = this.credentialString(datetime);
47 var qs = {
48 'X-Amz-Date': datetime,
49 'X-Amz-Algorithm': this.algorithm,
50 'X-Amz-Credential': credentials.accessKeyId + '/' + credString,
51 'X-Amz-Expires': this.request.headers[expiresHeader],
52 'X-Amz-SignedHeaders': this.signedHeaders()
53 };
54
55 if (credentials.sessionToken) {
56 qs['X-Amz-Security-Token'] = credentials.sessionToken;
57 }
58
59 if (this.request.headers['Content-Type']) {
60 qs['Content-Type'] = this.request.headers['Content-Type'];
61 }
62 if (this.request.headers['Content-MD5']) {
63 qs['Content-MD5'] = this.request.headers['Content-MD5'];
64 }
65 if (this.request.headers['Cache-Control']) {
66 qs['Cache-Control'] = this.request.headers['Cache-Control'];
67 }
68
69 // need to pull in any other X-Amz-* headers
70 AWS.util.each.call(this, this.request.headers, function(key, value) {
71 if (key === expiresHeader) return;
72 if (this.isSignableHeader(key)) {
73 var lowerKey = key.toLowerCase();
74 // Metadata should be normalized
75 if (lowerKey.indexOf('x-amz-meta-') === 0) {
76 qs[lowerKey] = value;
77 } else if (lowerKey.indexOf('x-amz-') === 0) {
78 qs[key] = value;
79 }
80 }
81 });
82
83 var sep = this.request.path.indexOf('?') >= 0 ? '&' : '?';
84 this.request.path += sep + AWS.util.queryParamsToString(qs);
85 },
86
87 authorization: function authorization(credentials, datetime) {
88 var parts = [];
89 var credString = this.credentialString(datetime);
90 parts.push(this.algorithm + ' Credential=' +
91 credentials.accessKeyId + '/' + credString);
92 parts.push('SignedHeaders=' + this.signedHeaders());
93 parts.push('Signature=' + this.signature(credentials, datetime));
94 return parts.join(', ');
95 },
96
97 signature: function signature(credentials, datetime) {
98 var signingKey = v4Credentials.getSigningKey(
99 credentials,
100 datetime.substr(0, 8),
101 this.request.region,
102 this.serviceName,
103 this.signatureCache
104 );
105 return AWS.util.crypto.hmac(signingKey, this.stringToSign(datetime), 'hex');
106 },
107
108 stringToSign: function stringToSign(datetime) {
109 var parts = [];
110 parts.push('AWS4-HMAC-SHA256');
111 parts.push(datetime);
112 parts.push(this.credentialString(datetime));
113 parts.push(this.hexEncodedHash(this.canonicalString()));
114 return parts.join('\n');
115 },
116
117 canonicalString: function canonicalString() {
118 var parts = [], pathname = this.request.pathname();
119 if (this.serviceName !== 's3' && this.signatureVersion !== 's3v4') pathname = AWS.util.uriEscapePath(pathname);
120
121 parts.push(this.request.method);
122 parts.push(pathname);
123 parts.push(this.request.search());
124 parts.push(this.canonicalHeaders() + '\n');
125 parts.push(this.signedHeaders());
126 parts.push(this.hexEncodedBodyHash());
127 return parts.join('\n');
128 },
129
130 canonicalHeaders: function canonicalHeaders() {
131 var headers = [];
132 AWS.util.each.call(this, this.request.headers, function (key, item) {
133 headers.push([key, item]);
134 });
135 headers.sort(function (a, b) {
136 return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1;
137 });
138 var parts = [];
139 AWS.util.arrayEach.call(this, headers, function (item) {
140 var key = item[0].toLowerCase();
141 if (this.isSignableHeader(key)) {
142 var value = item[1];
143 if (typeof value === 'undefined' || value === null || typeof value.toString !== 'function') {
144 throw AWS.util.error(new Error('Header ' + key + ' contains invalid value'), {
145 code: 'InvalidHeader'
146 });
147 }
148 parts.push(key + ':' +
149 this.canonicalHeaderValues(value.toString()));
150 }
151 });
152 return parts.join('\n');
153 },
154
155 canonicalHeaderValues: function canonicalHeaderValues(values) {
156 return values.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '');
157 },
158
159 signedHeaders: function signedHeaders() {
160 var keys = [];
161 AWS.util.each.call(this, this.request.headers, function (key) {
162 key = key.toLowerCase();
163 if (this.isSignableHeader(key)) keys.push(key);
164 });
165 return keys.sort().join(';');
166 },
167
168 credentialString: function credentialString(datetime) {
169 return v4Credentials.createScope(
170 datetime.substr(0, 8),
171 this.request.region,
172 this.serviceName
173 );
174 },
175
176 hexEncodedHash: function hash(string) {
177 return AWS.util.crypto.sha256(string, 'hex');
178 },
179
180 hexEncodedBodyHash: function hexEncodedBodyHash() {
181 var request = this.request;
182 if (this.isPresigned() && this.serviceName === 's3' && !request.body) {
183 return 'UNSIGNED-PAYLOAD';
184 } else if (request.headers['X-Amz-Content-Sha256']) {
185 return request.headers['X-Amz-Content-Sha256'];
186 } else {
187 return this.hexEncodedHash(this.request.body || '');
188 }
189 },
190
191 unsignableHeaders: [
192 'authorization',
193 'content-type',
194 'content-length',
195 'user-agent',
196 expiresHeader,
197 'expect',
198 'x-amzn-trace-id'
199 ],
200
201 isSignableHeader: function isSignableHeader(key) {
202 if (key.toLowerCase().indexOf('x-amz-') === 0) return true;
203 return this.unsignableHeaders.indexOf(key) < 0;
204 },
205
206 isPresigned: function isPresigned() {
207 return this.request.headers[expiresHeader] ? true : false;
208 }
209
210});
211
212/**
213 * @api private
214 */
215module.exports = AWS.Signers.V4;