plugins/mod_auth_internal_hashed.lua

1
-- Prosody IM
2
-- Copyright (C) 2008-2010 Matthew Wild
3
-- Copyright (C) 2008-2010 Waqas Hussain
4
-- Copyright (C) 2010 Jeff Mitchell
5
--
6
-- This project is MIT/X11 licensed. Please see the
7
-- COPYING file in the source package for more information.
8
--
9
 
10
local datamanager = require "util.datamanager";
11
local log = require "util.logger".init("auth_internal_hashed");
12
local type = type;
13
local error = error;
14
local ipairs = ipairs;
15
local hashes = require "util.hashes";
16
local jid_bare = require "util.jid".bare;
17
local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
18
local config = require "core.configmanager";
19
local usermanager = require "core.usermanager";
20
local generate_uuid = require "util.uuid".generate;
21
local new_sasl = require "util.sasl".new;
22
local nodeprep = require "util.encodings".stringprep.nodeprep;
23
local hosts = hosts;
24
 
25
-- COMPAT w/old trunk: remove these two lines before 0.8 release
26
local hmac_sha1 = require "util.hmac".sha1;
27
local sha1 = require "util.hashes".sha1;
28
 
29
local to_hex;
30
do
31
	local function replace_byte_with_hex(byte)
32
		return ("%02x"):format(byte:byte());
33
	end
34
	function to_hex(binary_string)
35
		return binary_string:gsub(".", replace_byte_with_hex);
36
	end
37
end
38
 
39
local from_hex;
40
do
41
	local function replace_hex_with_byte(hex)
42
		return string.char(tonumber(hex, 16));
43
	end
44
	function from_hex(hex_string)
45
		return hex_string:gsub("..", replace_hex_with_byte);
46
	end
47
end
48
 
49
 
50
local prosody = _G.prosody;
51
 
52
-- Default; can be set per-user
53
local iteration_count = 4096;
54
 
55
function new_hashpass_provider(host)
56
	local provider = { name = "internal_hashed" };
57
	log("debug", "initializing hashpass authentication provider for host '%s'", host);
58
 
59
	function provider.test_password(username, password)
60
		local credentials = datamanager.load(username, host, "accounts") or {};
61
	
62
		if credentials.password ~= nil and string.len(credentials.password) ~= 0 then
63
			if credentials.password ~= password then
64
				return nil, "Auth failed. Provided password is incorrect.";
65
			end
66
 
67
			if provider.set_password(username, credentials.password) == nil then
68
				return nil, "Auth failed. Could not set hashed password from plaintext.";
69
			else
70
				return true;
71
			end
72
		end
73
 
74
		if credentials.iteration_count == nil or credentials.salt == nil or string.len(credentials.salt) == 0 then
75
			return nil, "Auth failed. Stored salt and iteration count information is not complete.";
76
		end
77
		
78
		-- convert hexpass to stored_key and server_key
79
		-- COMPAT w/old trunk: remove before 0.8 release
80
		if credentials.hashpass then
81
			local salted_password = from_hex(credentials.hashpass);
82
			credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
83
			credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
84
			credentials.hashpass = nil
85
			datamanager.store(username, host, "accounts", credentials);
86
		end
87
		
88
		local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count);
89
		
90
		local stored_key_hex = to_hex(stored_key);
91
		local server_key_hex = to_hex(server_key);
92
		
93
		if valid and stored_key_hex == credentials.stored_key and server_key_hex == credentials.server_key then
94
			return true;
95
		else
96
			return nil, "Auth failed. Invalid username, password, or password hash information.";
97
		end
98
	end
99
 
100
	function provider.set_password(username, password)
101
		local account = datamanager.load(username, host, "accounts");
102
		if account then
103
			account.salt = account.salt or generate_uuid();
104
			account.iteration_count = account.iteration_count or iteration_count;
105
			local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, account.salt, account.iteration_count);
106
			local stored_key_hex = to_hex(stored_key);
107
			local server_key_hex = to_hex(server_key);
108
			
109
			account.stored_key = stored_key_hex
110
			account.server_key = server_key_hex
111
 
112
			account.password = nil;
113
			return datamanager.store(username, host, "accounts", account);
114
		end
115
		return nil, "Account not available.";
116
	end
117
 
118
	function provider.user_exists(username)
119
		local account = datamanager.load(username, host, "accounts");
120
		if not account then
121
			log("debug", "account not found for username '%s' at host '%s'", username, module.host);
122
			return nil, "Auth failed. Invalid username";
123
		end
124
		return true;
125
	end
126
 
127
	function provider.create_user(username, password)
128
		if password == nil then
129
			return datamanager.store(username, host, "accounts", {});
130
		end
131
		local salt = generate_uuid();
132
		local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
133
		local stored_key_hex = to_hex(stored_key);
134
		local server_key_hex = to_hex(server_key);
135
		return datamanager.store(username, host, "accounts", {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count});
136
	end
137
 
138
	function provider.get_sasl_handler()
139
		local realm = module:get_option("sasl_realm") or module.host;
140
		local testpass_authentication_profile = {
141
			plain_test = function(sasl, username, password, realm)
142
				local prepped_username = nodeprep(username);
143
				if not prepped_username then
144
					log("debug", "NODEprep failed on username: %s", username);
145
					return "", nil;
146
				end
147
				return usermanager.test_password(prepped_username, realm, password), true;
148
			end,
149
			scram_sha_1 = function(sasl, username, realm)
150
				local credentials = datamanager.load(username, host, "accounts");
151
				if not credentials then return; end
152
				if credentials.password then
153
					usermanager.set_password(username, credentials.password, host);
154
					credentials = datamanager.load(username, host, "accounts");
155
					if not credentials then return; end
156
				end
157
				
158
				-- convert hexpass to stored_key and server_key
159
				-- COMPAT w/old trunk: remove before 0.8 release
160
				if credentials.hashpass then
161
					local salted_password = from_hex(credentials.hashpass);
162
					credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
163
					credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
164
					credentials.hashpass = nil
165
					datamanager.store(username, host, "accounts", credentials);
166
				end
167
			
168
				local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt;
169
				stored_key = stored_key and from_hex(stored_key);
170
				server_key = server_key and from_hex(server_key);
171
				return stored_key, server_key, iteration_count, salt, true;
172
			end
173
		};
174
		return new_sasl(realm, testpass_authentication_profile);
175
	end
176
	
177
	return provider;
178
end
179
 
180
module:add_item("auth-provider", new_hashpass_provider(module.host));