Skip to content

Commit a737d93

Browse files
authored
Merge pull request #1054 from chrisradek/fix/signatureCache
Fixes issue where 2 clients from the same service would share a signature cache.
2 parents 8ba7874 + 09d0807 commit a737d93

File tree

5 files changed

+89
-10
lines changed

5 files changed

+89
-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: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ var inherit = AWS.util.inherit;
66
*/
77
var cachedSecret = {};
88

9+
/**
10+
* @api private
11+
*/
12+
var cacheQueue = [];
13+
14+
/**
15+
* @api private
16+
*/
17+
var maxCacheEntries = 50;
18+
919
/**
1020
* @api private
1121
*/
@@ -97,8 +107,18 @@ AWS.Signers.V4 = inherit(AWS.Signers.RequestSigner, {
97107

98108
signature: function signature(credentials, datetime) {
99109
var cache = null;
110+
var cacheIdentifier = this.serviceName + (this.getServiceClientId() ? '_' + this.getServiceClientId() : '');
100111
if (this.signatureCache) {
101-
var cache = cachedSecret[this.serviceName];
112+
var cache = cachedSecret[cacheIdentifier];
113+
// If there isn't already a cache entry, we'll be adding one
114+
if (!cache) {
115+
cacheQueue.push(cacheIdentifier);
116+
if (cacheQueue.length > maxCacheEntries) {
117+
// remove the oldest entry (may not be last one used)
118+
delete cachedSecret[cacheQueue.shift()];
119+
}
120+
}
121+
102122
}
103123
var date = datetime.substr(0, 8);
104124

@@ -117,13 +137,13 @@ AWS.Signers.V4 = inherit(AWS.Signers.RequestSigner, {
117137
return AWS.util.crypto.hmac(kCredentials, this.stringToSign(datetime), 'hex');
118138
}
119139

120-
cachedSecret[this.serviceName] = {
140+
cachedSecret[cacheIdentifier] = {
121141
region: this.request.region, date: date,
122142
key: kCredentials, akid: credentials.accessKeyId
123143
};
124144
}
125145

126-
var key = cachedSecret[this.serviceName].key;
146+
var key = cachedSecret[cacheIdentifier].key;
127147
return AWS.util.crypto.hmac(key, this.stringToSign(datetime), 'hex');
128148
},
129149

test/signers/v4.spec.coffee

Lines changed: 49 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,26 @@ describe 'AWS.Signers.V4', ->
7584
calls = AWS.util.crypto.hmac.calls
7685
callCount = calls.length
7786

87+
it 'will cache a maximum of 50 clients', (done) ->
88+
maxCacheEntries = 50
89+
clientSigners = (buildSignerFromService() for i in [0..maxCacheEntries-1])
90+
callCount = calls.length
91+
#Get signature for all clients to store them in cache
92+
(clientSigners[i].signature(creds, datetime) for i in [0..clientSigners.length-1])
93+
expect(calls.length).to.equal(callCount + (5 * maxCacheEntries))
94+
#Signer should use cache
95+
callCount = calls.length
96+
clientSigners[0].signature(creds, datetime)
97+
expect(calls.length).to.equal(callCount + 1)
98+
#add a new signer, pushing past cache limit
99+
newestSigner = buildSignerFromService()
100+
#old signer shouldn't be using cache anymore
101+
callCount = calls.length
102+
clientSigners[0].signature(creds, datetime)
103+
expect(calls.length).to.equal(callCount + 5)
104+
done()
105+
106+
#Calling signer.signature should call hmac 1 time when caching, and 5 times when not caching
78107
it 'caches subsequent requests', ->
79108
signer.signature(creds, datetime)
80109
expect(calls.length).to.equal(callCount + 1)
@@ -108,6 +137,24 @@ describe 'AWS.Signers.V4', ->
108137
signer.signature(creds, newDatetime)
109138
expect(calls.length).to.equal(callCount + 5)
110139

140+
it 'uses a different cache if client is different', ->
141+
signer1 = buildSignerFromService()
142+
callCount = calls.length
143+
signer1.signature(creds, datetime)
144+
expect(calls.length).to.equal(callCount + 5)
145+
signer2 = buildSignerFromService()
146+
callCount = calls.length
147+
signer2.signature(creds, datetime)
148+
expect(calls.length).to.equal(callCount + 5)
149+
150+
it 'works when using the same client', ->
151+
signer1 = buildSignerFromService()
152+
callCount = calls.length
153+
signer1.signature(creds, datetime)
154+
expect(calls.length).to.equal(callCount + 5)
155+
signer1.signature(creds, datetime)
156+
expect(calls.length).to.equal(callCount + 6)
157+
111158
describe 'stringToSign', ->
112159
it 'should sign correctly generated input string', ->
113160
expect(signer.stringToSign(datetime)).to.equal 'AWS4-HMAC-SHA256\n' +

0 commit comments

Comments
 (0)