diff --git a/bin/telebit-remote.js b/bin/telebit-remote.js index 818bc7d..2fe7c71 100755 --- a/bin/telebit-remote.js +++ b/bin/telebit-remote.js @@ -685,6 +685,7 @@ function parseConfig(err, text) { , protected: { // alg will be filled out automatically jwk: state.pub + , kid: false , nonce: nonce , url: newAccountUrl } diff --git a/bin/telebitd.js b/bin/telebitd.js index e98f403..9d6bf2d 100755 --- a/bin/telebitd.js +++ b/bin/telebitd.js @@ -590,9 +590,14 @@ function jwtEggspress(req, res, next) { // TODO verify if possible console.warn("[warn] JWT is not verified yet"); + // A failed JWS should cause a failed JWT + if (false !== req.trusted) { + req.trusted = true; + } next(); } +// TODO switch to Keypairs.js / Keyfetch.js function verifyJws(jwk, jws) { return keypairs.export({ jwk: jwk }).then(function (pem) { var alg = 'SHA' + jws.header.alg.replace(/[^\d]+/i, ''); @@ -632,9 +637,8 @@ function jwsEggspress(req, res, next) { } if (req.jws.header.jwk) { if (kid) { - // TODO kid and jwk are mutually exclusive - //res.send({ error: { message: "jws protected header must not include both 'kid' and 'jwk'" } }); - //return; + res.send({ error: { message: "jws protected header must not include both 'kid' and 'jwk'" } }); + return; } kid = req.jws.header.jwk.kid; p = Keypairs.thumbprint({ jwk: req.jws.header.jwk }).then(function (thumb) { @@ -699,6 +703,10 @@ function jwsEggspress(req, res, next) { }); }); }).then(function () { + // a failed JWT should cause a failed JWS + if (false !== req.trusted) { + req.trusted = req.jws.trusted; + } next(); }); } @@ -1038,10 +1046,19 @@ function handleApi() { app.head('/acme/new-nonce', controllers.newNonce); app.get('/acme/new-nonce', controllers.newNonce); app.post('/acme/new-acct', controllers.newAccount); - app.use(/\b(relay)\b/, controllers.relay); - app.get(/\b(config)\b/, getConfigOnly); - app.use(/\b(init|config)\b/, initOrConfig); - app.use(/\b(restart)\b/, restart); + function mustTrust(req, res, next) { + // TODO public routes should be explicitly marked + // trusted should be the default + if (req.trusted) { next(); } + res.statusCode = 400; + res.send({"error":{"message": "this type of requests must be encoded as a jws payload" + + " and signed by a trusted account holder"}}); + return; + } + app.use(/\b(relay)\b/, mustTrust, controllers.relay); + app.get(/\b(config)\b/, mustTrust, getConfigOnly); + app.use(/\b(init|config)\b/, mustTrust, initOrConfig); + app.use(/\b(restart)\b/, mustTrust, restart); // Position is important with eggspress // This should stay here, right before the other methods @@ -1050,14 +1067,14 @@ function handleApi() { // // With proper config // - app.use(/\b(http)\b/, controllers.http); - app.use(/\b(tcp)\b/, controllers.tcp); - app.use(/\b(save|commit)\b/, saveAndCommit); - app.use(/\b(ssh)\b/, controllers.ssh); - app.use(/\b(enable)\b/, enable); - app.use(/\b(disable)\b/, disable); - app.use(/\b(status)\b/, getStatus); - app.use(/\b(list)\b/, listSuccess); + app.use(/\b(http)\b/, mustTrust, controllers.http); + app.use(/\b(tcp)\b/, mustTrust, controllers.tcp); + app.use(/\b(save|commit)\b/, mustTrust, saveAndCommit); + app.use(/\b(ssh)\b/, mustTrust, controllers.ssh); + app.use(/\b(enable)\b/, mustTrust, enable); + app.use(/\b(disable)\b/, mustTrust, disable); + app.use(/\b(status)\b/, mustTrust, getStatus); + app.use(/\b(list)\b/, mustTrust, listSuccess); app.use('/', function (req, res) { res.send({"error":{"message":"unrecognized rpc"}}); }); diff --git a/lib/rc/index.js b/lib/rc/index.js index 5df7c72..66579f2 100644 --- a/lib/rc/index.js +++ b/lib/rc/index.js @@ -160,6 +160,7 @@ module.exports.create = function (state) { , protected: { // alg will be filled out automatically jwk: state.pub + , kid: false , nonce: require('crypto').randomBytes(16).toString('hex') // TODO get from server // TODO make localhost exceptional , url: RC.resolve(reqOpts.path)