Skip to content

Commit d535f96

Browse files
committed
Fixes issue where 2 clients from the same service would share a signature cache.
This issue would result in the same signed key to be used by 2 different clients if they were configured to use the same region and accessKeyId. (Very unlikely if possible). This issue also resulted in the signature cache being cleared anytime the client making a request was alternated with another client of the same service type. Fixes #1020 Fixes #1052
1 parent 078e478 commit d535f96

File tree

5 files changed

+52
-10
lines changed

5 files changed

+52
-10
lines changed

lib/event_listeners.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,20 +136,22 @@ AWS.EventListeners = {
136136
});
137137

138138
addAsync('SIGN', 'sign', function SIGN(req, done) {
139-
if (!req.service.api.signatureVersion) return done(); // none
139+
var service = req.service;
140+
if (!service.api.signatureVersion) return done(); // none
140141

141-
req.service.config.getCredentials(function (err, credentials) {
142+
service.config.getCredentials(function (err, credentials) {
142143
if (err) {
143144
req.response.error = err;
144145
return done();
145146
}
146147

147148
try {
148149
var date = AWS.util.date.getDate();
149-
var SignerClass = req.service.getSignerClass(req);
150+
var SignerClass = service.getSignerClass(req);
150151
var signer = new SignerClass(req.httpRequest,
151-
req.service.api.signingName || req.service.api.endpointPrefix,
152-
req.service.config.signatureCache);
152+
service.api.signingName || service.api.endpointPrefix,
153+
service.config.signatureCache);
154+
signer.setServiceClientId(service._clientId);
153155

154156
// clear old authorization headers
155157
delete req.httpRequest.headers['Authorization'];

lib/service.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var AWS = require('./core');
22
var Api = require('./model/api');
33
var regionConfig = require('./region_config');
44
var inherit = AWS.util.inherit;
5+
var clientCount = 0;
56

67
/**
78
* The service class representing an AWS service.
@@ -32,6 +33,7 @@ AWS.Service = inherit({
3233
enumerable: false,
3334
configurable: true
3435
});
36+
svc._clientId = ++clientCount;
3537
return svc;
3638
}
3739
this.initialize(config);

lib/signers/request_signer.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ var inherit = AWS.util.inherit;
77
AWS.Signers.RequestSigner = inherit({
88
constructor: function RequestSigner(request) {
99
this.request = request;
10+
},
11+
12+
setServiceClientId: function setServiceClientId(id) {
13+
this.serviceClientId = id;
14+
},
15+
16+
getServiceClientId: function getServiceClientId() {
17+
return this.serviceClientId;
1018
}
1119
});
1220

lib/signers/v4.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ AWS.Signers.V4 = inherit(AWS.Signers.RequestSigner, {
9797

9898
signature: function signature(credentials, datetime) {
9999
var cache = null;
100+
var cacheIdentifier = this.serviceName + (this.getServiceClientId() ? '_' + this.getServiceClientId() : '');
100101
if (this.signatureCache) {
101-
var cache = cachedSecret[this.serviceName];
102+
var cache = cachedSecret[cacheIdentifier];
102103
}
103104
var date = datetime.substr(0, 8);
104105

@@ -117,13 +118,13 @@ AWS.Signers.V4 = inherit(AWS.Signers.RequestSigner, {
117118
return AWS.util.crypto.hmac(kCredentials, this.stringToSign(datetime), 'hex');
118119
}
119120

120-
cachedSecret[this.serviceName] = {
121+
cachedSecret[cacheIdentifier] = {
121122
region: this.request.region, date: date,
122123
key: kCredentials, akid: credentials.accessKeyId
123124
};
124125
}
125126

126-
var key = cachedSecret[this.serviceName].key;
127+
var key = cachedSecret[cacheIdentifier].key;
127128
return AWS.util.crypto.hmac(key, this.stringToSign(datetime), 'hex');
128129
},
129130

test/signers/v4.spec.coffee

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,17 @@ buildRequest = ->
1212
req.httpRequest
1313

1414
buildSigner = (request, signatureCache) ->
15-
return new AWS.Signers.V4(request || buildRequest(), 'dynamodb', signatureCache || true)
15+
if typeof signatureCache != 'boolean'
16+
signatureCache = true
17+
return new AWS.Signers.V4(request || buildRequest(), 'dynamodb', signatureCache)
18+
19+
buildSignerFromService = (signatureCache) ->
20+
if typeof signatureCache != 'boolean'
21+
signatureCache = true
22+
ddb = new AWS.DynamoDB({region: 'region', endpoint: 'localhost', apiVersion: '2011-12-05'})
23+
signer = buildSigner(null, signatureCache)
24+
signer.setServiceClientId(ddb._clientId)
25+
return signer
1626

1727
describe 'AWS.Signers.V4', ->
1828
date = new Date(1935346573456)
@@ -34,7 +44,6 @@ describe 'AWS.Signers.V4', ->
3444
req = buildRequest()
3545
signer = buildSigner(req)
3646
expect(signer.request).to.equal(req)
37-
3847
describe 'addAuthorization', ->
3948
headers = {
4049
'Content-Type': 'application/x-amz-json-1.0',
@@ -75,6 +84,8 @@ describe 'AWS.Signers.V4', ->
7584
calls = AWS.util.crypto.hmac.calls
7685
callCount = calls.length
7786

87+
88+
#Calling signer.signature should call hmac 1 time when caching, and 5 times when not caching
7889
it 'caches subsequent requests', ->
7990
signer.signature(creds, datetime)
8091
expect(calls.length).to.equal(callCount + 1)
@@ -108,6 +119,24 @@ describe 'AWS.Signers.V4', ->
108119
signer.signature(creds, newDatetime)
109120
expect(calls.length).to.equal(callCount + 5)
110121

122+
it 'uses a different cache if client is different', ->
123+
signer1 = buildSignerFromService()
124+
callCount = calls.length
125+
signer1.signature(creds, datetime)
126+
expect(calls.length).to.equal(callCount + 5)
127+
signer2 = buildSignerFromService()
128+
callCount = calls.length
129+
signer2.signature(creds, datetime)
130+
expect(calls.length).to.equal(callCount + 5)
131+
132+
it 'works when using the same client', ->
133+
signer1 = buildSignerFromService()
134+
callCount = calls.length
135+
signer1.signature(creds, datetime)
136+
expect(calls.length).to.equal(callCount + 5)
137+
signer1.signature(creds, datetime)
138+
expect(calls.length).to.equal(callCount + 6)
139+
111140
describe 'stringToSign', ->
112141
it 'should sign correctly generated input string', ->
113142
expect(signer.stringToSign(datetime)).to.equal 'AWS4-HMAC-SHA256\n' +

0 commit comments

Comments
 (0)