New HMAC token authentication module for Prosody.
local base64 = require "util.encodings".base64;
local digest = require "openssl.digest";
local hmac = require "openssl.hmac";
local luatz = require "luatz";
local otp = require "otp";
local DIGEST_TYPE = "SHA256";
local OTP_DEVIATION = 1;
local OTP_DIGITS = 8;
local OTP_INTERVAL = 30;
local nonce_cache = {};
function check_nonce(jid, otp, nonce)
-- We cache all nonces used per OTP, to ensure that a token cannot be used
-- more than once.
--
-- We assume that the OTP is valid in the current time window. This is the
-- case because we only call check_nonce *after* the OTP has been verified.
--
-- We only store one OTP per JID, so if a new OTP comes in, we wipe the
-- previous OTP and its cached nonces.
if nonce_cache[jid] == nil or nonce_cache[jid][otp] == nil then
nonce_cache[jid] = {}
nonce_cache[jid][otp] = {}
nonce_cache[jid][otp][nonce] = true
return true;
end
if nonce_cache[jid][otp][nonce] == true then
return false;
else
nonce_cache[jid][otp][nonce] = true;
return true;
end
end
function verify_token(username, password, realm, otp_seed, token_secret, log)
local totp = otp.new_totp_from_key(otp_seed, OTP_DIGITS, OTP_INTERVAL)
local token = string.match(password, "(%d+) ")
local otp = token:sub(1,8)
local nonce = token:sub(9)
local signature = base64.decode(string.match(password, " (.+)"))
local jid = username.."@"..realm
if totp:verify(otp, OTP_DEVIATION, luatz.gmtime(luatz.time())) then
-- log("debug", "**** THE OTP WAS VERIFIED ****** ");
local hmac_ctx = hmac.new(token_secret, DIGEST_TYPE)
if signature == hmac_ctx:final(otp..nonce..jid) then
-- log("debug", "**** THE KEY WAS VERIFIED ****** ");
if check_nonce(jid, otp, nonce) then
-- log("debug", "**** THE NONCE WAS VERIFIED ****** ");
return true;
end
end
end
-- log("debug", "**** VERIFICATION FAILED ****** ");
return false;
end
return {
OTP_DEVIATION = OTP_DIGITS,
OTP_DIGITS = OTP_DIGITS,
OTP_INTERVAL = OTP_INTERVAL,
DIGEST_TYPE = DIGEST_TYPE,
verify_token = verify_token;
}