| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- // Manual live-database verification of the SA-16 rehash path (not part
- // of the meson test suite): boots a real SQLite store through Inversion
- // + DatabaseConfigurator exactly as SpryModule does, registers a user,
- // force-stales their stored hash by writing one at INTERACTIVE limits,
- // then authenticates and asserts the stored hash was transparently
- // rewritten at current MODERATE parameters — same password, epoch NOT
- // bumped, login succeeding throughout.
- //
- // Build & run (from the Spry checkout, against a built libspry):
- // valac tests/RehashProbe.vala vapi/libsodium.vapi -o /tmp/rehash-probe \
- // --vapidir vapi --vapidir <builddir>/src \
- // --pkg spry-0.2 --pkg statum-0.1 --pkg astralis-0.1 \
- // --pkg inversion-0.1 --pkg invercargill-1 --pkg invercargill-json \
- // --pkg invercargill-sql --pkg invercargill-sql-inversion \
- // --pkg sqlite3 --pkg libxml-2.0 --pkg json-glib-1.0 \
- // --pkg glib-2.0 --pkg gobject-2.0 --pkg gio-2.0 \
- // -X -lsodium -X -L<builddir>/src -X -lspry-0.2 -X -Wl,-rpath,<builddir>/src
- // /tmp/rehash-probe /tmp/rehash-probe.sqlite
- using Inversion;
- using InvercargillSqlInversion;
- using Invercargill.Expressions;
- using Invercargill.DataStructures;
- using InvercargillSql.Orm;
- using Spry;
- using Spry.Authentication;
- int failures = 0;
- void check(bool condition, string label) {
- if (condition) {
- print("PASS %s\n", label);
- } else {
- failures++;
- print("FAIL %s\n", label);
- }
- }
- async int probe(string db_path) {
- GLib.FileUtils.remove(db_path);
- var container = new Container();
- try {
- var db = container.configure_with<DatabaseConfigurator>();
- db.register_connection(@"sqlite://$(db_path)");
- db.add_migration<Authentication.Migrations.M0001_Initial>();
- db.add_migration<Authentication.Migrations.M0002_SessionsValidAfter>();
- db.add_entity<Authentication.UserEntity>(Authentication.UserEntity.entity_mapping);
- db.add_entity<Authentication.UserPermissionEntity>(Authentication.UserPermissionEntity.entity_mapping);
- db.add_projection<Authentication.UserProjection>(Authentication.UserProjection.projection_mapping);
- container.register_scoped<Authentication.UserService>();
- db.migrate_on_startup();
- container.initialise();
- } catch (Error e) {
- printerr("probe wiring threw: %s\n", e.message);
- return 1;
- }
- string password = "live rehash probe password";
- try {
- var scope = container.create_scope();
- var users = scope.resolve<UserService>();
- var orm = scope.resolve<OrmSession>();
- var registered = yield users.register_user("probe-user", "probe@example.com",
- "Probe", "User", new GLib.DateTime.utc(1990, 1, 1, 0, 0, 0.0), password, true);
- int64 id = registered.id;
- check(!UserService.hash_needs_rehash(load_hash(orm, id)),
- "registration stores current parameters");
- // Force-stale the stored hash (INTERACTIVE limits), exactly as
- // an old deployment's row looks after a parameter bump.
- string stale = Sodium.PasswordHashing.hash(password,
- Sodium.PasswordHashing.OPSLIMIT_INTERACTIVE,
- Sodium.PasswordHashing.MEMLIMIT_INTERACTIVE);
- store_hash(orm, id, stale);
- check(UserService.hash_needs_rehash(load_hash(orm, id)),
- "stale parameters are recognised as needing a rehash");
- var epoch_before = load_epoch(orm, id);
- var logged_in = yield users.authenticate_user("probe-user", password);
- var epoch_after = load_epoch(orm, id);
- string upgraded = load_hash(orm, id);
- check(logged_in != null, "login succeeds against a stale hash");
- check(!UserService.hash_needs_rehash(upgraded),
- "the stored hash was rewritten at current parameters on login");
- check(Sodium.PasswordHashing.check(upgraded, password),
- "the same password verifies against the rewritten hash");
- check(epoch_after.compare(epoch_before) == 0,
- "the session-validity epoch was untouched by the rehash");
- check((yield users.authenticate_user("probe-user", "wrong password entirely")) == null,
- "a wrong password is rejected against the upgraded hash");
- } catch (Error e) {
- printerr("probe threw: %s\n", e.message);
- return 1;
- }
- print("---- live rehash probe: %d failures ----\n", failures);
- return failures > 0 ? 1 : 0;
- }
- int main(string[] args) {
- string db_path = args.length > 1 ? args[1] : "/tmp/rehash-probe.sqlite";
- var loop = new GLib.MainLoop();
- int result = 1;
- probe.begin(db_path, (o, r) => {
- result = probe.end(r);
- loop.quit();
- });
- loop.run();
- return result;
- }
- private UserEntity load_user(OrmSession orm, int64 user_id) throws Error {
- var user = orm.query<UserEntity>()
- .where(expr("id == $0", elem<int64?>(user_id)))
- .first();
- if (user == null) {
- throw new GLib.IOError.FAILED(@"no user row for id $(user_id)");
- }
- return (!)user;
- }
- private string load_hash(OrmSession orm, int64 user_id) throws Error {
- return load_user(orm, user_id).password_hash;
- }
- private GLib.DateTime load_epoch(OrmSession orm, int64 user_id) throws Error {
- return load_user(orm, user_id).sessions_valid_after;
- }
- private void store_hash(OrmSession orm, int64 user_id, string hash) throws Error {
- var user = load_user(orm, user_id);
- user.password_hash = hash;
- orm.update<UserEntity>(user);
- }
|