diff --git a/bin/load_gen b/bin/load_gen index 980e35d6a..99f492bfb 100755 --- a/bin/load_gen +++ b/bin/load_gen @@ -40,6 +40,9 @@ * tool, which is capable of analysing the maximum active users that * a browserid deployment can support */ +const winston = require('winston'); + + // option processing with optimist var argv = require('optimist') .usage('Apply load to a BrowserID server.\nUsage: $0', [ "foo" ]) @@ -64,6 +67,7 @@ if (args.h) { process.exit(1); } + // global configuration const configuration = { verifier: args.v ? args.v : args.s + "/verify", @@ -103,10 +107,12 @@ var activity = { // users forget their password once every 4 weeks probability: (1.0 / (40 * 28.0)) }, +*/ "add_email": { // users add a new email address once every 2 weeks probability: (1.0 / (40 * 14.0)) }, +/* "reauth": { // users must re-authenticate to browser id once a week // (once every two weeks per device) @@ -182,6 +188,7 @@ function poll() { if (!args.o || act !== 'include_only') { outstanding++; activity[act].startFunc(configuration, function(err) { + if (err) winston.error(err); outstanding--; if (undefined === completed[act]) completed[act] = [ 0, 0 ]; completed[act][err ? 1 : 0]++; diff --git a/lib/load_gen/activities/add_email.js b/lib/load_gen/activities/add_email.js index f93d4a84f..aa12ba00d 100644 --- a/lib/load_gen/activities/add_email.js +++ b/lib/load_gen/activities/add_email.js @@ -38,49 +38,29 @@ * user with an active session adding a new email with browserid. */ const -wcli = require("../../libs/wsapi_client.js"), -userdb = require("./user_db.js"), -winston = require('winston'); - -function authenticated(cfg, context, email, password, cb) { - wcli.get(cfg, '/wsapi/am_authed', context, {}, function(r) { - if (r.body === 'true') cb(); - else { - wcli.post( - cfg, '/wsapi/authenticate_user', context, - { email: email, pass: password }, - function(r) { - if (r.code != 200 || r.body !== "true") { - winston.error('authentication failure: ' + r.code + "/" + r.body); - } - cb(); - }); - } - }); -} +wcli = require("../../wsapi_client.js"), +userdb = require("../user_db.js"), +winston = require('winston'), +prepare = require('../prepare.js'); exports.startFunc = function(cfg, cb) { - // 1. RP includes include.js - // 2. users' browser loads all code associated with dialog - // 3. /wsapi/add_email is called to stage the email address - // 4. in the load testing environment, we make a call to the server to get - // the email verification token - // 5. /prove is called on the server, passing in the authentication token - // 6. /manage is called on the server as the user's page transitions from - // the verify screen to the manage screen. - // 7. /wsapi/sync_emails is called from the client upon verification - // (this is a bug, isn't it?) - // 8. /wsapi/set_key is called from the client to inform the server of the - // user's public key for this new email (XXX: this will go away when we migrate to certificates - // and instead, the server will be asked to sign the user's public key.) - // 9. the RP calls /verify to verify a generated assertion + // 1. RP includes include.js + // 2. session_context is called + // 3. list_emails is called + // 4. stage_email is called + // 5. email_addition_status is invoked some number of times while the dialog polls + // 6. landing page is loaded: + // 6a. session_context + // 6b. complete_email_addition + // 7. email_addition_status returns 'complete' + // 8. a key is generated and added // first let's get an existing user var user = userdb.getExistingUser(); if (!user) { winston.warn("can't achieve desired concurrency! not enough users!"); - return cb(false); + return cb("not enough users"); } // user will be "released" once we're done with her. @@ -98,44 +78,67 @@ exports.startFunc = function(cfg, cb) { // pick one of the user's emails that we'll use var email = userdb.addEmailToUser(user); - var keypair = userdb.addKeyToUserCtx(context, email); + var origin = userdb.any(user.sites); - authenticated(cfg, context, user.emails[0], user.password, function() { + prepare.auth(cfg, user, context, user.emails[0], function(err) { + if (err) return cb(err); // stage them - wcli.post(cfg, '/wsapi/add_email', context, { + wcli.post(cfg, '/wsapi/stage_email', context, { email: email, - pubkey: keypair.pub, site: userdb.any(user.sites) }, function (r) { if (r.code !== 200) { - winston.error('failed to add email: ' + email + ' to existing user ' + user.emails[0]); - console.log(r); - return cb(false); + var msg = 'failed to add email: ' + email + ' to existing user ' + + user.emails[0]; + winston.error(msg); + return cb(msg); } // now get the verification secret wcli.get(cfg, '/wsapi/fake_verification', context, { email: email }, function (r) { if (r.code !== 200) { - winston.error('failed to fetch verification token for email: ' + email); - return cb(false); + var err ='failed to fetch verification token for email: ' + email; + winston.error(err); + return cb(err); } var token = r.body; + // and simulate clickthrough - wcli.get(cfg, '/wsapi/prove_email_ownership', context, { + wcli.post(cfg, '/wsapi/complete_email_addition', context, { token: token }, function (r) { - if (r.code !== 200 || r.body !== 'true') { - winston.error('failed to prove email owndership for: ' + email + ' (' + token + ')'); - return cb(false); + try { + if (r.code !== 200) throw "bad response code"; + if (JSON.parse(r.body).success !== true) throw "success? no."; + } catch (e) { + var err = 'failed to complete email addition for: ' + email + ' (' + token + '): ' + e.toString(); + winston.error(err); + process.exit(1); + return cb(err); } + // and now we should call registration status to complete the // process - wcli.get(cfg, '/wsapi/registration_status', context, { + wcli.get(cfg, '/wsapi/email_addition_status', context, { + email: email }, function(r) { - var rv = (r.code === 200 && r.body === '"complete"'); - if (!rv) winston.error("registration_status failed during signup: " + JSON.stringify(r)); - cb(rv); + try { + if (r.code !== 200) throw "bad response code"; + if (JSON.parse(r.body).status !== 'complete') throw "addition not complete? wrong: " + r.body; + } catch(e) { + var err = "registration_status failed during signup: " + e.toString(); + winston.error(err); + return cb(err); + } + + // now generate a key + prepare.authAndKey(cfg, user, context, email, function(err) { + if (err) return cb(err); + prepare.genAssertionAndVerify(cfg, user, context, email, origin, function(err) { + cb(err); + }); + }); }); }); }); diff --git a/lib/load_gen/activities/reauth.js b/lib/load_gen/activities/reauth.js index de9e9f2fb..6a5085cb9 100644 --- a/lib/load_gen/activities/reauth.js +++ b/lib/load_gen/activities/reauth.js @@ -89,7 +89,7 @@ function syncEmails(cfg, context, cb) { } exports.startFunc = function(cfg, cb) { - // 1. RP includes include.js + // 1. RP includes include.js // 2. users' browser loads all code associated with dialog // 3. in page javascript calls CSRF to get a CSRF token // 4. /wsapi/authenticate_user is called once the user enters credentials diff --git a/lib/load_gen/activities/signin.js b/lib/load_gen/activities/signin.js index 079293de0..e75613f13 100644 --- a/lib/load_gen/activities/signin.js +++ b/lib/load_gen/activities/signin.js @@ -58,7 +58,7 @@ exports.startFunc = function(cfg, cb) { if (!user) { winston.warn("can't achieve desired concurrency! not enough users!"); - return cb(false); + return cb("not enough users"); } // unlock the user when we're done with them @@ -79,39 +79,10 @@ exports.startFunc = function(cfg, cb) { var origin = userdb.any(user.sites); // establish session context and authenticate if needed - prepare(cfg, user, context, email, function(err) { - try { - serverTime = new Date(context.session.server_time); - wcli.get(cfg, '/wsapi/list_emails', context, undefined, function (r) { - // just verify that we got a JSON object, we don't care about - // the contents so much - try { - if (!typeof JSON.parse(r.body) === 'object') throw 'bogus response'; - } catch(e) { - return cb(false); - } - - var assertion = crypto.getAssertion({ - now: serverTime, - secretKey: context.keys[email].keyPair.secretKey, - cert: context.keys[email].cert, - audience: origin, - email: email - }); - - wcli.post(cfg, '/verify', {}, { - audience: origin, - assertion: assertion - }, function (r) { - try { - cb(JSON.parse(r.body).status === 'okay' ? undefined : "verification failed"); - } catch(e) { - return cb(e.toString); - } - }); - }); - } catch(e) { - cb(e.toString()); - } + prepare.authAndKey(cfg, user, context, email, function(err) { + if (err) return cb(err); + prepare.genAssertionAndVerify(cfg, user, context, email, origin, function(err) { + cb(err); + }); }); }; diff --git a/lib/load_gen/activities/signup.js b/lib/load_gen/activities/signup.js index e5c650595..e628b3958 100644 --- a/lib/load_gen/activities/signup.js +++ b/lib/load_gen/activities/signup.js @@ -96,7 +96,6 @@ exports.startFunc = function(cfg, cb) { if (r.code !== 200) return cb(false); // now get the verification secret wcli.get(cfg, '/wsapi/fake_verification', context, { - email: email }, function (r) { if (r.code !== 200) return cb(false); @@ -106,11 +105,12 @@ exports.startFunc = function(cfg, cb) { pass: user.password }, function (r) { r.body = JSON.parse(r.body); - if (r.code !== 200 || r.body.success !== true) return cb(false); - + if (r.code !== 200 || r.body.success !== true) { + return cb("failed to complete user creation"); + } // and now let's prepare this idenity in this context (get a keypair // and certify it) - prepare(cfg, user, context, email, function(err) { + prepare.authAndKey(cfg, user, context, email, function(err) { cb(err); }); }); diff --git a/lib/load_gen/prepare.js b/lib/load_gen/prepare.js index f5e29d8ef..cfb891898 100644 --- a/lib/load_gen/prepare.js +++ b/lib/load_gen/prepare.js @@ -1,35 +1,30 @@ -// the common steps required to "prepare" an identity for use inside -// a simulated browser context live here. This includes: -// -// 1. authenticating if requried -// 2. generating a keypair if required -// 3. certifying that keypair -// 4. storing all this crap on the session. +// some common procedures. const wcli = require("../wsapi_client.js"), -userdb = require("./user_db.js"); +userdb = require("./user_db.js"), +crypto = require("./crypto.js"); -module.exports = function(cfg, user, ctx, email, cb) { - function doAuth(lcb) { - if (ctx.session && ctx.session.authenticated) { - lcb(); - } else { - wcli.post( - cfg, '/wsapi/authenticate_user', ctx, - { email: email, pass: user.password }, - function(r) { - if (JSON.parse(r.body).success !== true) return cb("failed to authenticate"); - ctx.session.authenticated = true; - lcb(); - } - ); - } - }; +exports.auth = function(cfg, user, ctx, email, cb) { + if (ctx.session && ctx.session.authenticated) { + cb(); + } else { + wcli.post( + cfg, '/wsapi/authenticate_user', ctx, + { email: email, pass: user.password }, + function(r) { + if (JSON.parse(r.body).success !== true) return cb("failed to authenticate"); + ctx.session.authenticated = true; + cb(); + } + ); + } +}; - function genKey(lcb) { +exports.authAndKey = function(cfg, user, ctx, email, cb) { + function genKey(cb) { if (ctx.keys && ctx.keys[email]) { - lcb(); + cb(); } else { var keypair = userdb.addKeyToUserCtx(ctx, email); // and now let's certify the pubkey @@ -39,15 +34,46 @@ module.exports = function(cfg, user, ctx, email, cb) { }, function(resp) { if (typeof resp.body !== 'string') return cb("can't certify key"); userdb.addCertToUserCtx(ctx, email, resp.body); - lcb(); + cb(); }); } }; - doAuth(function() { - genKey(function() { - // all done! - cb(); + exports.auth(cfg, user, ctx, email, function(err) { + if (err) return cb(err); + genKey(cb); + }); +}; + +exports.genAssertionAndVerify = function(cfg, user, ctx, email, audience, cb) { + var serverTime = new Date(ctx.session.server_time); + wcli.get(cfg, '/wsapi/list_emails', ctx, undefined, function (r) { + // just verify that we got a JSON object, we don't care about + // the contents so much + try { + if (!typeof JSON.parse(r.body) === 'object') throw 'bogus response'; + } catch(e) { + return cb(e.toString()); + } + + var assertion = crypto.getAssertion({ + now: serverTime, + secretKey: ctx.keys[email].keyPair.secretKey, + cert: ctx.keys[email].cert, + audience: audience, + email: email + }); + + wcli.post(cfg, '/verify', {}, { + audience: audience, + assertion: assertion + }, function (r) { + try { + cb(JSON.parse(r.body).status === 'okay' ? undefined : "verification failed"); + } catch(e) { + return cb("response body: " + r.body + " error: " + e.toString()); + } }); }); -}; \ No newline at end of file +} + diff --git a/lib/verifier/certassertion.js b/lib/verifier/certassertion.js index e7beb9c8f..4e54eb943 100644 --- a/lib/verifier/certassertion.js +++ b/lib/verifier/certassertion.js @@ -77,7 +77,7 @@ function https_complete_get(host, url, successCB, errorCB) { }); }).on('error', function(e) { - console.log(e.toString()); + logger.warn(e.toString()); errorCB(e); }); } @@ -103,7 +103,7 @@ function retrieveHostPublicKey(host, successCB, errorCB) { // FIXME do we need to check hm:Host? var pk_location = null; - + // get the public key location var links = parsedDoc["Link"]; if (links instanceof Array) { @@ -203,7 +203,6 @@ function verify(assertion, audience, successCB, errorCB, pkRetriever) { // primary? if (theIssuer != config.get('hostname')) { // then the email better match the issuer - console.log(principal); if (!principal.email.match("@" + theIssuer + "$")) return errorCB(); }