Merge pull request #47 from dannycoates/signCert

implemented /sign with hawk credentials
This commit is contained in:
Zach Carter 2013-07-03 10:44:40 -07:00
Родитель 37e424629a 63e9e50f40
Коммит 0ff1610a20
8 изменённых файлов: 137 добавлений и 112 удалений

Просмотреть файл

@ -73,6 +73,7 @@ exports.create = function(data, cb) {
// create user account
function(key, cb) {
kv.store.set(userKey, {
email: data.email,
params: data.params,
verifier: data.verifier,
salt: data.salt,
@ -196,11 +197,25 @@ exports.finishLoginWithSRP = function (sessionId, A, M1, cb) {
},
// create signToken
util.getSignToken,
// create temporary account token doc
function(token, next) {
signToken = token;
addSignToken(uid, token, next);
},
function(next) {
util.signCertKeys(Buffer(signToken, 'hex'), next);
},
function(keys, next) {
kv.cache.set(
keys.tokenId.toString('base64') + '/hawk',
{
key: keys.reqHMACkey.toString('base64'),
algorithm: 'sha256',
uid: uid,
signToken: signToken
},
next
);
},
// delete session doc
function(next) {
kv.cache.delete(sessKey, next);
@ -526,6 +541,13 @@ function getUser(userId, cb) {
}
exports.getUser = getUser;
function getHawkCredentials(tokenId, cb) {
kv.cache.get(tokenId + '/hawk', function (err, x) {
cb(err, x ? x.value : null);
});
}
exports.getHawkCredentials = getHawkCredentials;
// This account principle associated with a singing token
// The principle is the userId combined with the IDP domain
// e.g. 1234@lcip.org

Просмотреть файл

@ -1,30 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Hapi = require('hapi');
const account = require('./account');
// Prerequesites can be included in a route's configuration and will run
// before the route's handler is called. Results are set on
// the request.pre object using the method's name for the property name,
// or otherwise using the value of the "assign" property.
//
// Methods specified as strings are helpers. Check ./helpers.js for
// definitions.
module.exports = {
principle: {
method: function(request, next) {
if(request.payload.email) return next(request.payload.email);
var token = request.payload.token;
if (!token) next(Hapi.Error.badRequest('MissingSignToken'));
account.getPrinciple(token, function(err, principle) {
if (err) next(err);
else next(principle);
});
},
assign: 'principle'
}
};

Просмотреть файл

@ -74,6 +74,16 @@ function srpResponseKeys(srpK, cb) {
});
}
// Derive a tokenId and reqHMACkey from the signToken
function signCertKeys(signToken, cb) {
hkdf(signToken, 'signCertificate', null, 2 * 32, function (key) {
cb(null, {
tokenId: key.slice(0, 32),
reqHMACkey: key.slice(32, 64)
});
});
}
// generates the encrypted bundle for getSignToken2
// params should be Buffer instances
//
@ -111,5 +121,6 @@ module.exports = {
getSignToken: getSignToken,
getResetToken: getResetToken,
srpResponseKeys: srpResponseKeys,
srpSignTokenBundle: srpSignTokenBundle
srpSignTokenBundle: srpSignTokenBundle,
signCertKeys: signCertKeys
};

Просмотреть файл

@ -19,6 +19,7 @@
"dependencies": {
"bigint": "0.4.2",
"hapi": "1.6.0",
"hawk": "1.0.0",
"hkdf": "0.0.1",
"compute-cluster": "0.0.7",
"jwcrypto": "0.4.3",

Просмотреть файл

@ -6,7 +6,6 @@ const Hapi = require('hapi');
const fs = require('fs');
const CC = require('compute-cluster');
const config = require('../lib/config').root();
const prereqs = require('../lib/prereqs');
const hour = 1000 * 60 * 60;
const T = Hapi.types;
@ -67,14 +66,15 @@ var routes = [
path: '/sign',
config: {
handler: sign,
pre: [ prereqs.principle ],
auth: {
strategy: 'hawk',
payload: 'required'
},
tags: ["account"],
validate: {
payload: {
email: T.String().without('token'), // for testing only
publicKey: T.String().required(),
duration: T.Number().integer().min(0).max(24 * hour).required(),
token: T.String().without('email')
publicKey: Hapi.types.String().required(),
duration: Hapi.types.Number().integer().min(0).max(24 * hour).required()
}
}
}
@ -138,24 +138,6 @@ var routes = [
}
}
},
{
method: 'POST',
path: '/signToken',
config: {
tags: ["account"],
handler: getSignToken,
validate: {
payload: {
accountToken: T.String().required()
},
response: {
schema: {
signToken: T.String().required()
}
}
}
}
},
{
method: 'POST',
path: '/resetToken',
@ -216,21 +198,25 @@ function create(request) {
}
function sign(request) {
var principle = request.pre.principle;
cc.enqueue(
{
email: principle,
publicKey: request.payload.publicKey,
duration: request.payload.duration
},
function (err, result) {
if (err || result.err) {
request.reply(Hapi.error.internal('Unable to sign certificate', err || result.err));
}
else {
request.reply(result.cert);
}
account.getUser(
request.auth.credentials.uid,
function (err, user) {
if (err) { return request.reply(Hapi.error.internal('Unable to sign certificate', err)); }
cc.enqueue(
{
email: user.email,
publicKey: request.payload.publicKey,
duration: request.payload.duration
},
function (err, result) {
if (err || result.err) {
request.reply(Hapi.error.internal('Unable to sign certificate', err || result.err));
}
else {
request.reply(result);
}
}
);
}
);
}
@ -279,21 +265,6 @@ function finishLogin(request) {
}
}
function getSignToken(request) {
account.getSignToken(
request.payload.accountToken,
function (err, result) {
if (err) {
request.reply(err);
}
else {
request.reply(result);
}
}
);
}
function getResetToken(request) {
account.getResetToken(

Просмотреть файл

@ -5,6 +5,7 @@
const path = require('path');
const Hapi = require('hapi');
const toobusy = require('toobusy');
const account = require('./lib/account');
module.exports = function (config, routes, log) {
@ -25,6 +26,11 @@ module.exports = function (config, routes, log) {
}
);
server.auth('hawk', {
scheme: 'hawk',
getCredentialsFunc: account.getHawkCredentials
});
server.addRoutes(routes);
server.app.log = log;

Просмотреть файл

@ -8,6 +8,7 @@ const routes = require('../routes');
const srp = require('../lib/srp');
const srpParams = require('../lib/srp_group_params');
const util = require('../lib/util');
const hawk = require('hawk');
const noop = function () {};
@ -229,3 +230,44 @@ TestClient.prototype.loginSRP = function (email, password, cb) {
}.bind(this)
);
};
TestClient.prototype.sign = function (publicKey, duration, signToken, hashPayload, cb) {
util.signCertKeys(
Buffer(signToken, 'hex'),
function (err, keys) {
var credentials = {
id: keys.tokenId.toString('base64'),
key: keys.reqHMACkey.toString('base64'),
algorithm: 'sha256'
};
var payload = {
publicKey: publicKey,
duration: duration
};
var verify = {
credentials: credentials,
contentType: 'application/json'
};
if (hashPayload) {
verify.payload = JSON.stringify(payload);
}
var header = hawk.client.header('http://localhost/sign', 'POST', verify);
this.makeRequest(
'POST',
'/sign',
{
headers: {
Authorization: header.field,
Host: 'localhost',
'Content-Type': 'application/json'
},
payload: payload
},
function (res) {
cb(null, res.result);
}
);
}.bind(this)
);
};

Просмотреть файл

@ -49,6 +49,7 @@ describe('user', function() {
try {
assert(!err);
assert.equal(TEST_WRAPKB, keys.wrapKb);
signToken = keys.signToken;
} catch (e) {
return done(e);
}
@ -215,23 +216,6 @@ describe('user', function() {
});
});
it('should get signToken', function(done) {
testClient.makeRequest('POST', '/signToken', {
payload: {
accountToken: accountToken
}
}, function(res) {
try {
assert.equal(res.statusCode, 200);
signToken = res.result.signToken;
assert.ok(res.result.signToken);
} catch (e) {
return done(e);
}
done();
});
});
it('should generate a pubkey', function(done) {
jwcrypto.generateKeypair({ algorithm: "RS", keysize: 64 }, function(err, keypair) {
pubkey = keypair.publicKey;
@ -240,17 +224,35 @@ describe('user', function() {
});
it('should sign a pubkey', function(done) {
testClient.makeRequest('POST', '/sign', {
payload: {
token: signToken,
publicKey: pubkey.serialize(),
duration: 50000
}
}, function(res) {
testClient.sign(pubkey.serialize(), 50000, signToken, true, function (err, result) {
try {
assert.equal(res.statusCode, 200);
assert.ok(result);
// check for rough format of a cert
assert.equal(res.result.split(".").length, 3);
assert.equal(result.cert.split(".").length, 3);
} catch (e) {
return done(e);
}
done();
});
});
it('should not sign with an invalid signToken', function(done) {
testClient.sign(pubkey.serialize(), 50000, Buffer(32), true, function (err, result) {
try {
assert.equal(result.code, 401);
assert.equal(result.message, 'Unknown credentials');
} catch (e) {
return done(e);
}
done();
});
});
it('should not sign without a hawk verified payload', function(done) {
testClient.sign(pubkey.serialize(), 50000, signToken, false, function (err, result) {
try {
assert.equal(result.code, 401);
assert.equal(result.message, 'Payload is invalid');
} catch (e) {
return done(e);
}