From b73df8098f261ecbd4bc5ba689f9766a1a75f9a0 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sun, 15 Jul 2018 03:22:12 +0200 Subject: [PATCH] auth-filters: generate secret securely This is much better than having the user generate it themselves. Signed-off-by: Jason A. Donenfeld --- filters/gentoo-ldap-authentication.lua | 53 +++++++++++++++++++++----- filters/simple-authentication.lua | 50 ++++++++++++++++++++---- 2 files changed, 85 insertions(+), 18 deletions(-) diff --git a/filters/gentoo-ldap-authentication.lua b/filters/gentoo-ldap-authentication.lua index 3b6564b..b4d98c2 100644 --- a/filters/gentoo-ldap-authentication.lua +++ b/filters/gentoo-ldap-authentication.lua @@ -5,7 +5,13 @@ -- -- lualdap >= 1.2 -- +-- luaposix +-- -- +local sysstat = require("posix.sys.stat") +local unistd = require("posix.unistd") +local crypto = require("crypto") +local lualdap = require("lualdap") -- @@ -21,11 +27,9 @@ local protected_repos = { portage = "dev" } - --- All cookies will be authenticated based on this secret. Make it something --- totally random and impossible to guess. It should be large. -local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM" - +-- Set this to a path this script can write to for storing a persistent +-- cookie secret, which should be guarded. +local secret_filename = "/var/cache/cgit/auth-secret" -- @@ -102,8 +106,6 @@ end -- -- -local lualdap = require("lualdap") - function gentoo_ldap_user_groups(username, password) -- Ensure the user is alphanumeric if username == nil or username:match("%W") then @@ -231,7 +233,38 @@ end -- -- -local crypto = require("crypto") +local secret = nil + +-- Loads a secret from a file, creates a secret, or returns one from memory. +function get_secret() + if secret ~= nil then + return secret + end + local secret_file = io.open(secret_filename, "r") + if secret_file == nil then + local old_umask = sysstat.umask(63) + local temporary_filename = secret_filename .. ".tmp." .. crypto.hex(crypto.rand.bytes(16)) + local temporary_file = io.open(temporary_filename, "w") + if temporary_file == nil then + os.exit(177) + end + temporary_file:write(crypto.hex(crypto.rand.bytes(32))) + temporary_file:close() + unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. + unistd.unlink(temporary_filename) + sysstat.umask(old_umask) + secret_file = io.open(secret_filename, "r") + end + if secret_file == nil then + os.exit(177) + end + secret = secret_file:read() + secret_file:close() + if secret:len() ~= 64 then + os.exit(177) + end + return secret +end -- Returns value of cookie if cookie is valid. Otherwise returns nil. function validate_value(expected_field, cookie) @@ -271,7 +304,7 @@ function validate_value(expected_field, cookie) end -- Lua hashes strings, so these comparisons are time invariant. - if hmac ~= crypto.hmac.digest("sha256", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, secret) then + if hmac ~= crypto.hmac.digest("sha256", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, get_secret()) then return nil end @@ -296,7 +329,7 @@ function secure_value(field, value, expiration) value = url_encode(value) field = url_encode(field) authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt - authstr = authstr .. "|" .. crypto.hmac.digest("sha256", authstr, secret) + authstr = authstr .. "|" .. crypto.hmac.digest("sha256", authstr, get_secret()) return authstr end diff --git a/filters/simple-authentication.lua b/filters/simple-authentication.lua index 596c041..bf35632 100644 --- a/filters/simple-authentication.lua +++ b/filters/simple-authentication.lua @@ -3,7 +3,12 @@ -- Requirements: -- luacrypto >= 0.3 -- +-- luaposix +-- -- +local sysstat = require("posix.sys.stat") +local unistd = require("posix.unistd") +local crypto = require("crypto") -- @@ -31,11 +36,9 @@ local users = { bob = "ilikelua" } --- All cookies will be authenticated based on this secret. Make it something --- totally random and impossible to guess. It should be large. -local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM" - - +-- Set this to a path this script can write to for storing a persistent +-- cookie secret, which should be guarded. +local secret_filename = "/var/cache/cgit/auth-secret" -- -- @@ -191,7 +194,38 @@ end -- -- -local crypto = require("crypto") +local secret = nil + +-- Loads a secret from a file, creates a secret, or returns one from memory. +function get_secret() + if secret ~= nil then + return secret + end + local secret_file = io.open(secret_filename, "r") + if secret_file == nil then + local old_umask = sysstat.umask(63) + local temporary_filename = secret_filename .. ".tmp." .. crypto.hex(crypto.rand.bytes(16)) + local temporary_file = io.open(temporary_filename, "w") + if temporary_file == nil then + os.exit(177) + end + temporary_file:write(crypto.hex(crypto.rand.bytes(32))) + temporary_file:close() + unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. + unistd.unlink(temporary_filename) + sysstat.umask(old_umask) + secret_file = io.open(secret_filename, "r") + end + if secret_file == nil then + os.exit(177) + end + secret = secret_file:read() + secret_file:close() + if secret:len() ~= 64 then + os.exit(177) + end + return secret +end -- Returns value of cookie if cookie is valid. Otherwise returns nil. function validate_value(expected_field, cookie) @@ -231,7 +265,7 @@ function validate_value(expected_field, cookie) end -- Lua hashes strings, so these comparisons are time invariant. - if hmac ~= crypto.hmac.digest("sha256", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, secret) then + if hmac ~= crypto.hmac.digest("sha256", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, get_secret()) then return nil end @@ -256,7 +290,7 @@ function secure_value(field, value, expiration) value = url_encode(value) field = url_encode(field) authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt - authstr = authstr .. "|" .. crypto.hmac.digest("sha256", authstr, secret) + authstr = authstr .. "|" .. crypto.hmac.digest("sha256", authstr, get_secret()) return authstr end