Merge pull request #47 from dannycoates/signCert
implemented /sign with hawk credentials
This commit is contained in:
Коммит
0ff1610a20
|
@ -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'
|
||||
}
|
||||
};
|
13
lib/util.js
13
lib/util.js
|
@ -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);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче