Skip to content

Commit

Permalink
Add X-Amz-Content-Sha256 header to all SigV4 requests.
Browse files Browse the repository at this point in the history
Fixes #552
  • Loading branch information
AdityaManohar committed Apr 6, 2015
1 parent a9468fe commit 53bce85
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 57 deletions.
18 changes: 18 additions & 0 deletions lib/event_listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ AWS.EventListeners = {
new AWS.ParamValidator().validate(rules, req.params);
});

addAsync('COMPUTE_SHA256', 'afterBuild', function COMPUTE_SHA256(req, done) {
if (!req.service.api.signatureVersion) return done(); // none
if (req.service.getSignerClass(req) === AWS.Signers.V4) {
var body = req.httpRequest.body || '';
AWS.util.computeSha256(body, function(err, sha) {
if (err) {
done(err);
}
else {
req.httpRequest.headers['X-Amz-Content-Sha256'] = sha;
done();
}
});
} else {
done();
}
});

add('SET_CONTENT_LENGTH', 'afterBuild', function SET_CONTENT_LENGTH(req) {
if (req.httpRequest.headers['Content-Length'] === undefined) {
var length = AWS.util.string.byteLength(req.httpRequest.body);
Expand Down
35 changes: 1 addition & 34 deletions lib/services/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ AWS.util.update(AWS.S3.prototype, {
request.addListener('build', this.addContentType);
request.addListener('build', this.populateURI);
request.addListener('build', this.computeContentMd5);
request.onAsync('build', this.computeSha256);
request.addListener('build', this.computeSseCustomerKeyMd5);
request.addListener('afterBuild', this.addExpect100Continue);
request.removeListener('validate',
Expand Down Expand Up @@ -199,38 +198,6 @@ AWS.util.update(AWS.S3.prototype, {
}
},

/**
* @api private
*/
computeSha256: function computeSha256(req, done) {
if (req.service.getSignerClass(req) === AWS.Signers.V4) {
var body = req.httpRequest.body || '';

if (AWS.util.isNode()) {
var Stream = AWS.util.nodeRequire('stream').Stream;
var fs = AWS.util.nodeRequire('fs');
if (body instanceof Stream) {
if (typeof body.path === 'string') { // assume file object
body = fs.createReadStream(body.path);
} else { // TODO support other stream types
done(new Error('Non-file stream objects are ' +
'not supported with SigV4 in AWS.S3'));
return;
}
}
}

AWS.util.crypto.sha256(body, 'hex', function(err, sha) {
if (!err) {
req.httpRequest.headers['X-Amz-Content-Sha256'] = sha;
}
done(err);
});
} else {
done();
}
},

/**
* @api private
*/
Expand Down Expand Up @@ -420,7 +387,7 @@ AWS.util.update(AWS.S3.prototype, {
if (!request.params.Body) {
// no Content-MD5/SHA-256 if body is not provided
request.removeListener('build', request.service.computeContentMd5);
request.removeListener('build', request.service.computeSha256);
request.removeListener('afterBuild', AWS.EventListeners.Core.COMPUTE_SHA256);
}
},

Expand Down
2 changes: 2 additions & 0 deletions lib/signers/presign.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ AWS.Signers.Presign = inherit({
request.on('sign', signedUrlSigner);
request.removeListener('afterBuild',
AWS.EventListeners.Core.SET_CONTENT_LENGTH);
request.removeListener('afterBuild',
AWS.EventListeners.Core.COMPUTE_SHA256);

request.emit('beforePresign', [request]);

Expand Down
25 changes: 25 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,31 @@ var util = {
});
}
}
},

/**
* Compute SHA-256 checksums of streams
*
* @api private
*/
computeSha256: function computeSha256(body, done) {
if (AWS.util.isNode()) {
var Stream = AWS.util.nodeRequire('stream').Stream;
var fs = AWS.util.nodeRequire('fs');
if (body instanceof Stream) {
if (typeof body.path === 'string') { // assume file object
body = fs.createReadStream(body.path);
} else { // TODO support other stream types
return done(new Error('Non-file stream objects are ' +
'not supported with SigV4'));
}
}
}

AWS.util.crypto.sha256(body, 'hex', function(err, sha) {
if (err) done(err);
else done(null, sha);
});
}

};
Expand Down
49 changes: 28 additions & 21 deletions test/event_listeners.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -114,34 +114,41 @@ describe 'AWS.EventListeners', ->
expect(response.error).to.equal("ERROR")

describe 'afterBuild', ->
sendRequest = (body) ->
request = null
fs = null

sendRequest = (body, callback) ->
request = makeRequest()
request.removeAllListeners('sign')
request.on('build', (req) -> req.httpRequest.body = body)
request.build()
request
request.removeAllListeners 'sign'
request.on 'build', (req) -> req.httpRequest.body = body
if callback
request.send(callback)
else
request.send()
request

contentLength = (body) ->
sendRequest(body).httpRequest.headers['Content-Length']
describe 'adds Content-Length header', ->
contentLength = (body) ->
sendRequest(body).httpRequest.headers['Content-Length']

it 'builds Content-Length in the request headers for string content', ->
expect(contentLength('FOOBAR')).to.equal(6)
it 'builds Content-Length in the request headers for string content', ->
expect(contentLength('FOOBAR')).to.equal(6)

it 'builds Content-Length for string "0"', ->
expect(contentLength('0')).to.equal(1)
it 'builds Content-Length for string "0"', ->
expect(contentLength('0')).to.equal(1)

it 'builds Content-Length for utf-8 string body', ->
expect(contentLength('tï№')).to.equal(6)
it 'builds Content-Length for utf-8 string body', ->
expect(contentLength('tï№')).to.equal(6)

it 'builds Content-Length for buffer body', ->
expect(contentLength(new AWS.util.Buffer('tï№'))).to.equal(6)
it 'builds Content-Length for buffer body', ->
expect(contentLength(new AWS.util.Buffer('tï№'))).to.equal(6)

if AWS.util.isNode()
it 'builds Content-Length for file body', ->
fs = require('fs')
file = fs.createReadStream(__filename)
fileLen = fs.lstatSync(file.path).size
expect(contentLength(file)).to.equal(fileLen)
if AWS.util.isNode()
it 'builds Content-Length for file body', (done) ->
fs = require('fs')
file = fs.createReadStream(__filename)
sendRequest file, (err) ->
done()

describe 'sign', ->
it 'takes the request object as a parameter', ->
Expand Down
5 changes: 3 additions & 2 deletions test/signers/v4.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ describe 'AWS.Signers.V4', ->
date = new Date(1935346573456)
datetime = AWS.util.date.iso8601(date).replace(/[:\-]|\.\d{3}/g, '')
creds = null
signature = '0e24aaa0cc86cdc1b73143a147e731cf8c93d450cfcf1d18b2b7473f810b7a1d'
signature = '31fac5ed29db737fbcafac527470ca6d9283283197c5e6e94ea40ddcec14a9c1'
authorization = 'AWS4-HMAC-SHA256 Credential=akid/20310430/region/dynamodb/aws4_request, ' +
'SignedHeaders=host;x-amz-date;x-amz-security-token;x-amz-target;x-amz-user-agent, ' +
'SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-target;x-amz-user-agent, ' +
'Signature=' + signature
signer = null

Expand Down Expand Up @@ -124,6 +124,7 @@ describe 'AWS.Signers.V4', ->
it 'should return headers', ->
expect(signer.canonicalHeaders()).to.eql [
'host:localhost',
'x-amz-content-sha256:3128b8d4f3108b3e1677a38eb468d1c6dec926a58eaea235d034b9c71c3864d4',
'x-amz-date:' + datetime,
'x-amz-security-token:session',
'x-amz-target:DynamoDB_20111205.ListTables',
Expand Down

0 comments on commit 53bce85

Please sign in to comment.