Skip to content

Commit fef2bfa

Browse files
committed
Merge pull request #5 from eml/sha-256
add SHA-256 support
2 parents a0f8d20 + 1fee42f commit fef2bfa

File tree

6 files changed

+397
-5
lines changed

6 files changed

+397
-5
lines changed

lib/passport-http-oauth/strategies/consumer.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,15 @@ ConsumerStrategy.prototype.authenticate = function(req) {
293293
if (tokenSecret) { key += utils.encode(tokenSecret); }
294294
var computedSignature = utils.hmacsha1(key, base);
295295

296+
if (signature !== computedSignature) {
297+
return self.fail(self._challenge('signature_invalid'));
298+
}
299+
300+
} else if (signatureMethod === 'HMAC-SHA256') {
301+
var key = consumerSecret + '&';
302+
if (tokenSecret) { key += tokenSecret; }
303+
var computedSignature = utils.hmacsha256(key, base);
304+
296305
if (signature !== computedSignature) {
297306
return self.fail(self._challenge('signature_invalid'));
298307
}

lib/passport-http-oauth/strategies/token.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,24 @@ TokenStrategy.prototype.authenticate = function(req) {
226226
if (signature !== computedSignature) {
227227
return self.fail(self._challenge('signature_invalid'));
228228
}
229+
230+
} else if (signatureMethod == 'HMAC-SHA256') {
231+
var key = utils.encode(consumerSecret) + '&';
232+
if (tokenSecret) { key += utils.encode(tokenSecret); }
233+
var computedSignature = utils.hmacsha256(key, base);
234+
235+
if (signature !== computedSignature) {
236+
return self.fail(self._challenge('signature_invalid'));
237+
}
238+
229239
} else if (signatureMethod == 'PLAINTEXT') {
230240
var computedSignature = utils.plaintext(consumerSecret, tokenSecret);
231241

232242
if (signature !== computedSignature) {
233243
return self.fail(self._challenge('signature_invalid'));
234244
}
235-
} else{
245+
246+
} else {
236247
return self.fail(self._challenge('signature_method_rejected'), 400);
237248
}
238249

lib/passport-http-oauth/strategies/utils.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@ exports.hmacsha1 = function(key, text) {
185185
return crypto.createHmac('sha1', key).update(text).digest('base64')
186186
}
187187

188+
/**
189+
* Generate HMAC-SHA256 signature.
190+
*
191+
* @param {String} key
192+
* @param {String} text
193+
* @return {String}
194+
* @api private
195+
*/
196+
exports.hmacsha256 = function(key, text) {
197+
return crypto.createHmac('sha256', key).update(text).digest('base64')
198+
}
199+
188200
/**
189201
* Generate PLAINTEXT signature.
190202
*

test/strategies/consumer-test.js

Lines changed: 233 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,67 @@ vows.describe('ConsumerStrategy').addBatch({
314314
},
315315
},
316316
},
317-
317+
318+
'strategy handling a valid request without a request token using HMAC-256 signature': {
319+
topic: function() {
320+
var strategy = new ConsumerStrategy(
321+
// consumer callback
322+
function(consumerKey, done) {
323+
if (consumerKey == 'abc123') {
324+
done(null, { id: '1' }, 'ssh-secret');
325+
} else {
326+
done(new Error('something is wrong'))
327+
}
328+
},
329+
// token callback
330+
function(requestToken, done) {
331+
done(new Error('token callback should not be called'));
332+
}
333+
);
334+
return strategy;
335+
},
336+
337+
'after augmenting with actions': {
338+
topic: function(strategy) {
339+
var self = this;
340+
var req = {};
341+
strategy.success = function(user, info) {
342+
self.callback(null, user, info);
343+
}
344+
strategy.fail = function(challenge, status) {
345+
self.callback(new Error('should not be called'));
346+
}
347+
strategy.error = function(err) {
348+
self.callback(new Error('should not be called'));
349+
}
350+
351+
req.url = '/oauth/request_token';
352+
req.method = 'POST';
353+
req.headers = {};
354+
req.headers['host'] = '127.0.0.1:3000';
355+
req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="qlKeUBu8wDmP/L24e4SErAoSmyomhyHgiL9J3xnX9Xk="';
356+
req.query = url.parse(req.url, true).query;
357+
req.connection = { encrypted: false };
358+
process.nextTick(function () {
359+
strategy.authenticate(req);
360+
});
361+
},
362+
363+
'should not generate an error' : function(err, user, info) {
364+
assert.isNull(err);
365+
},
366+
'should authenticate' : function(err, user, info) {
367+
assert.equal(user.id, '1');
368+
},
369+
'should set scheme to OAuth' : function(err, user, info) {
370+
assert.equal(info.scheme, 'OAuth');
371+
},
372+
'should set callbackURL' : function(err, user, info) {
373+
assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
374+
},
375+
},
376+
},
377+
318378
'strategy handling a valid request without a request token placing credentials in header with all-caps scheme': {
319379
topic: function() {
320380
var strategy = new ConsumerStrategy(
@@ -654,6 +714,59 @@ vows.describe('ConsumerStrategy').addBatch({
654714
},
655715
},
656716
},
717+
718+
'strategy handling a valid request without a request token using HMAC-SHA256 signature where consumer secret is wrong': {
719+
topic: function() {
720+
var strategy = new ConsumerStrategy(
721+
// consumer callback
722+
function(consumerKey, done) {
723+
done(null, { id: '1' }, 'ssh-secret-wrong');
724+
},
725+
// token callback
726+
function(requestToken, done) {
727+
done(new Error('token callback should not be called'));
728+
}
729+
);
730+
return strategy;
731+
},
732+
733+
'after augmenting with actions': {
734+
topic: function(strategy) {
735+
var self = this;
736+
var req = {};
737+
strategy.success = function(user, info) {
738+
self.callback(new Error('should not be called'));
739+
}
740+
strategy.fail = function(challenge, status) {
741+
self.callback(null, challenge, status);
742+
}
743+
strategy.error = function(err) {
744+
self.callback(new Error('should not be called'));
745+
}
746+
747+
req.url = '/oauth/request_token';
748+
req.method = 'POST';
749+
req.headers = {};
750+
req.headers['host'] = '127.0.0.1:3000';
751+
req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="ignored"';
752+
req.query = url.parse(req.url, true).query;
753+
req.connection = { encrypted: false };
754+
process.nextTick(function () {
755+
strategy.authenticate(req);
756+
});
757+
},
758+
759+
'should not generate an error' : function(err, challenge, status) {
760+
assert.isNull(err);
761+
},
762+
'should respond with challenge' : function(err, challenge, status) {
763+
assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
764+
},
765+
'should respond with default status' : function(err, challenge, status) {
766+
assert.isUndefined(status);
767+
},
768+
},
769+
},
657770

658771
'strategy handling a valid request without a request token using unkown signature method': {
659772
topic: function() {
@@ -1003,7 +1116,72 @@ vows.describe('ConsumerStrategy').addBatch({
10031116
},
10041117
},
10051118
},
1006-
1119+
1120+
'strategy handling a valid request with a request token using HMAC-SHA256 signature': {
1121+
topic: function() {
1122+
var strategy = new ConsumerStrategy(
1123+
// consumer callback
1124+
function(consumerKey, done) {
1125+
if (consumerKey == 'abc123') {
1126+
done(null, { id: '1' }, 'ssh-secret');
1127+
} else {
1128+
done(new Error('something is wrong'))
1129+
}
1130+
},
1131+
// token callback
1132+
function(requestToken, done) {
1133+
if (requestToken == 'wM9YRRm5') {
1134+
done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
1135+
} else {
1136+
done(new Error('something is wrong'))
1137+
}
1138+
}
1139+
);
1140+
return strategy;
1141+
},
1142+
1143+
'after augmenting with actions': {
1144+
topic: function(strategy) {
1145+
var self = this;
1146+
var req = {};
1147+
strategy.success = function(user, info) {
1148+
self.callback(null, user, info);
1149+
}
1150+
strategy.fail = function(challenge, status) {
1151+
self.callback(new Error('should not be called'));
1152+
}
1153+
strategy.error = function(err) {
1154+
self.callback(new Error('should not be called'));
1155+
}
1156+
1157+
req.url = '/oauth/access_token';
1158+
req.method = 'POST';
1159+
req.headers = {};
1160+
req.headers['host'] = '127.0.0.1:3000';
1161+
req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="06Kn8VWWkoLLz2yKbtc1j8hVXjFMlORwVMedb5S3otE="';
1162+
req.query = url.parse(req.url, true).query;
1163+
req.connection = { encrypted: false };
1164+
process.nextTick(function () {
1165+
strategy.authenticate(req);
1166+
});
1167+
},
1168+
1169+
'should not generate an error' : function(err, user, info) {
1170+
assert.isNull(err);
1171+
},
1172+
'should authenticate' : function(err, user, info) {
1173+
assert.equal(user.id, '1');
1174+
},
1175+
'should set scheme to OAuth' : function(err, user, info) {
1176+
assert.equal(info.scheme, 'OAuth');
1177+
},
1178+
'should include token and verifier' : function(err, user, info) {
1179+
assert.equal(info.oauth.token, 'wM9YRRm5');
1180+
assert.equal(info.oauth.verifier, 'qriPjOnc');
1181+
},
1182+
},
1183+
},
1184+
10071185
'strategy handling a valid request with a request token where token callback supplies info': {
10081186
topic: function() {
10091187
var strategy = new ConsumerStrategy(
@@ -1399,6 +1577,59 @@ vows.describe('ConsumerStrategy').addBatch({
13991577
},
14001578
},
14011579

1580+
'strategy handling a valid request with a request token using HMAC-SHA256 signature where token secret is wrong': {
1581+
topic: function() {
1582+
var strategy = new ConsumerStrategy(
1583+
// consumer callback
1584+
function(consumerKey, done) {
1585+
done(null, { id: '1' }, 'ssh-secret');
1586+
},
1587+
// token callback
1588+
function(requestToken, done) {
1589+
done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF-wrong');
1590+
}
1591+
);
1592+
return strategy;
1593+
},
1594+
1595+
'after augmenting with actions': {
1596+
topic: function(strategy) {
1597+
var self = this;
1598+
var req = {};
1599+
strategy.success = function(user, info) {
1600+
self.callback(new Error('should not be called'));
1601+
}
1602+
strategy.fail = function(challenge, status) {
1603+
self.callback(null, challenge, status);
1604+
}
1605+
strategy.error = function(err) {
1606+
self.callback(new Error('should not be called'));
1607+
}
1608+
1609+
req.url = '/oauth/access_token';
1610+
req.method = 'POST';
1611+
req.headers = {};
1612+
req.headers['host'] = '127.0.0.1:3000';
1613+
req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="nobodycares"';
1614+
req.query = url.parse(req.url, true).query;
1615+
req.connection = { encrypted: false };
1616+
process.nextTick(function () {
1617+
strategy.authenticate(req);
1618+
});
1619+
},
1620+
1621+
'should not generate an error' : function(err, user, info) {
1622+
assert.isNull(err);
1623+
},
1624+
'should respond with challenge' : function(err, challenge, status) {
1625+
assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
1626+
},
1627+
'should respond with default status' : function(err, challenge, status) {
1628+
assert.isUndefined(status);
1629+
},
1630+
},
1631+
},
1632+
14021633
'strategy handling a valid request with a request token where consumer callback fails with an error': {
14031634
topic: function() {
14041635
var strategy = new ConsumerStrategy(

0 commit comments

Comments
 (0)