diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Spend a missing account's login on a real hash
The fixed SHA-512 stand-in matched only the documented setup, so a site
hashing with yescrypt, other rounds or bcrypt answered for missing and
real names in different times, and on OpenBSD it failed at once.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/extensions/harness.lua | 17 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | tests/extensions/test-auth.lua | 87 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 files changed, 68 insertions, 36 deletions
diff --git a/tests/extensions/harness.lua b/tests/extensions/harness.lua index fc83a73..3df4719 100644 --- a/tests/extensions/harness.lua +++ b/tests/extensions/harness.lua @@ -78,6 +78,23 @@ function harness.load(script) dofile(script) end +-- Load a script with some of its own text replaced first, for checks that +-- need the commented-out examples of its configuration switched on. Each +-- replacement has to match exactly once, so a reworded example fails here +-- rather than leaving the check to test the unedited configuration. +function harness.load_with(script, replacements) + local file = assert(io.open(script, "rb")) + local source = file:read("*a") + file:close() + for _, pair in ipairs(replacements) do + local start = source:find(pair[1], 1, true) + assert(start and not source:find(pair[1], start + 1, true), + "expected exactly one " .. pair[1]) + source = source:sub(1, start - 1) .. pair[2] .. source:sub(start + #pair[1]) + end + assert((loadstring or load)(source, "@" .. script))() +end + local unpack_args = unpack or table.unpack -- One whole filter round trip, an open with the given arguments, a write per diff --git a/tests/extensions/test-auth.lua b/tests/extensions/test-auth.lua index 97638bd..e0d2fe5 100644 --- a/tests/extensions/test-auth.lua +++ b/tests/extensions/test-auth.lua @@ -5,11 +5,11 @@ -- every check about this script's own logic, the cookie layout, the expiry -- and field rules, the redirect vetting and the action flows, rather than -- about OpenSSL. The signing secret is pinned by replacing get_secret, and --- the account and access lookups are populated through each variant's own --- channel, the documented account_hash and repo_userset swap points for the --- inline script and fixture files reached through a redirected io.open for --- the file one. Both variants carry the same data so the flow checks read --- identically for the two. +-- the accounts and access lists are filled through each variant's own +-- channel, its commented-out examples switched on for the inline script and +-- fixture files reached through a redirected io.open for the file one. Both +-- variants carry the same data so the flow checks read identically for the +-- two. local test_directory = arg[0]:match("^(.*)/") or "." local h = dofile(test_directory .. "/harness.lua") @@ -43,31 +43,31 @@ if variant == "file" then h.redirect_file("/etc/cgit-auth/repos", "auth-repos") end -h.load(script) +if variant == "inline" then + h.load_with(script, { + { "local protected_repos = {\n", "local protected_repos = {\n" .. + "\t[\"secret-repo\"] = { alice = true, Bob = true },\n" .. + "\t[\"empty-repo\"] = {},\n" .. + "\t[\"bare-repo\"] = {},\n" }, + { "local users = {\n", "local users = {\n" .. + "\tAlice = \"" .. stored_hash .. "\",\n" }, + }) +else + h.load(script) +end function get_secret() return string.rep("0123456789abcdef", 4) end -if variant == "inline" then - local accounts = { alice = stored_hash } - local access = { - ["secret-repo"] = { alice = true, bob = true }, - ["empty-repo"] = {}, - ["bare-repo"] = {}, - } - function account_hash(user) - if user == nil then - return nil - end - return accounts[user:lower()] - end - function repo_userset(repo) - if repo == nil then - return nil - end - return access[repo] - end +-- crypt is watched through its stand-in module, so a check can see whether +-- a login spent it and on which hash. +local unistd = require("posix.unistd") +local real_crypt = unistd.crypt +local crypt_settings = {} +unistd.crypt = function(trypass, setting) + crypt_settings[#crypt_settings + 1] = setting + return real_crypt(trypass, setting) end -- The lookups themselves, which are the part the two variants do not share. @@ -75,6 +75,8 @@ end local hash = account_hash("ALICE") h.equals("an account is found whatever its case", hash, stored_hash) h.equals("an unknown account is not", account_hash("nobody"), nil) +local _, stand_in = account_hash("nobody") +h.equals("it brings another account's hash to spend instead", stand_in, stored_hash) h.equals("a nil user is not", account_hash(nil), nil) local userset = repo_userset("secret-repo") @@ -278,27 +280,40 @@ out, ret = run_action("authenticate-post", { }) h.contains("an unknown user is told nothing different", out, "Set-Cookie: cgitauth=; ") +-- A failed login for a name with no account spends crypt on a configured +-- hash, the same work a real check costs, so timing tells nothing. +crypt_settings = {} +run_action("authenticate-post", { + method = "POST", + body = "username=nobody&password=x&redirect=" .. url_encode(way_back), +}) +h.equals("a missing account spends crypt on a configured hash", crypt_settings[1], stored_hash) + +if variant == "file" then + h.redirect_file("/etc/cgit-auth/users", "auth-users-missing") + crypt_settings = {} + run_action("authenticate-post", { + method = "POST", + body = "username=nobody&password=x&redirect=" .. url_encode(way_back), + }) + h.equals("with no accounts at all nothing is spent", #crypt_settings, 0) + h.redirect_file("/etc/cgit-auth/users", "auth-users") +end + -- crypt's work grows with the password, so one past the cap is turned down -- before crypt sees it. -local unistd = require("posix.unistd") -local real_crypt = unistd.crypt -local crypt_calls = 0 -unistd.crypt = function(...) - crypt_calls = crypt_calls + 1 - return real_crypt(...) -end +crypt_settings = {} out, ret = run_action("authenticate-post", { method = "POST", body = "username=Alice&password=" .. string.rep("a", 257) .. "&redirect=" .. url_encode(way_back), }) h.contains("an overlong password is turned down", out, "Set-Cookie: cgitauth=; ") -h.equals("without spending crypt on it", crypt_calls, 0) -out, ret = run_action("authenticate-post", { +h.equals("without spending crypt on it", #crypt_settings, 0) +run_action("authenticate-post", { method = "POST", body = "username=Alice&password=" .. string.rep("a", 256) .. "&redirect=" .. url_encode(way_back), }) -h.equals("a password at the cap is still checked", crypt_calls, 1) -unistd.crypt = real_crypt +h.equals("a password at the cap is still checked", #crypt_settings, 1) out, ret = run_action("authenticate-post", { method = "POST", |
