80 -- apply SASLprep |
80 -- apply SASLprep |
81 username = saslprep(username); |
81 username = saslprep(username); |
82 return username; |
82 return username; |
83 end |
83 end |
84 |
84 |
85 local function scram_sha_1(self, message) |
85 local function scram_gen(hash_name, H_f, HMAC_f) |
86 if not self.state then self["state"] = {} end |
86 local function scram_hash(self, message) |
|
87 if not self.state then self["state"] = {} end |
87 |
88 |
88 if not self.state.name then |
89 if not self.state.name then |
89 -- we are processing client_first_message |
90 -- we are processing client_first_message |
90 local client_first_message = message; |
91 local client_first_message = message; |
91 self.state["client_first_message"] = client_first_message; |
92 self.state["client_first_message"] = client_first_message; |
92 self.state["name"] = client_first_message:match("n=(.+),r=") |
93 self.state["name"] = client_first_message:match("n=(.+),r=") |
93 self.state["clientnonce"] = client_first_message:match("r=([^,]+)") |
94 self.state["clientnonce"] = client_first_message:match("r=([^,]+)") |
94 |
95 |
95 if not self.state.name or not self.state.clientnonce then |
96 if not self.state.name or not self.state.clientnonce then |
96 return "failure", "malformed-request"; |
97 return "failure", "malformed-request"; |
97 end |
98 end |
98 |
99 |
99 self.state.name = validate_username(self.state.name); |
100 self.state.name = validate_username(self.state.name); |
100 if not self.state.name then |
101 if not self.state.name then |
101 log("debug", "Username violates either SASLprep or contains forbidden character sequences.") |
102 log("debug", "Username violates either SASLprep or contains forbidden character sequences.") |
102 return "failure", "malformed-request", "Invalid username."; |
103 return "failure", "malformed-request", "Invalid username."; |
103 end |
104 end |
104 |
105 |
105 self.state["servernonce"] = generate_uuid(); |
106 self.state["servernonce"] = generate_uuid(); |
106 self.state["salt"] = generate_uuid(); |
107 |
|
108 -- retreive credentials |
|
109 if self.profile.plain then |
|
110 password, state = self.profile.plain(self.state.name, self.realm) |
|
111 if state == nil then return "failure", "not-authorized" |
|
112 elseif state == false then return "failure", "account-disabled" end |
|
113 |
|
114 password = saslprep(password); |
|
115 if not password then |
|
116 log("debug", "Password violates SASLprep."); |
|
117 return "failure", "not-authorized", "Invalid password." |
|
118 end |
|
119 self.state.salt = generate_uuid(); |
|
120 self.state.iteration_count = default_i; |
|
121 self.state.salted_password = Hi(HMAC_f, password, self.state.salt, default_i); |
|
122 elseif self.profile["scram-"..hash_name] then |
|
123 salted_password, iteration_count, salt, state = self.profile["scram-"..hash_name](self.state.name, self.realm); |
|
124 if state == nil then return "failure", "not-authorized" |
|
125 elseif state == false then return "failure", "account-disabled" end |
|
126 |
|
127 self.state.salted_password = salted_password; |
|
128 self.state.iteration_count = iteration_count; |
|
129 self.state.salt = salt |
|
130 end |
107 |
131 |
108 local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i; |
132 local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count; |
109 self.state["server_first_message"] = server_first_message; |
133 self.state["server_first_message"] = server_first_message; |
110 return "challenge", server_first_message |
134 return "challenge", server_first_message |
111 else |
135 else |
112 if type(message) ~= "string" then return "failure", "malformed-request" end |
136 if type(message) ~= "string" then return "failure", "malformed-request" end |
113 -- we are processing client_final_message |
137 -- we are processing client_final_message |
114 local client_final_message = message; |
138 local client_final_message = message; |
115 |
139 |
116 self.state["proof"] = client_final_message:match("p=(.+)"); |
140 self.state["proof"] = client_final_message:match("p=(.+)"); |
117 self.state["nonce"] = client_final_message:match("r=(.+),p="); |
141 self.state["nonce"] = client_final_message:match("r=(.+),p="); |
118 self.state["channelbinding"] = client_final_message:match("c=(.+),r="); |
142 self.state["channelbinding"] = client_final_message:match("c=(.+),r="); |
119 if not self.state.proof or not self.state.nonce or not self.state.channelbinding then |
143 if not self.state.proof or not self.state.nonce or not self.state.channelbinding then |
120 return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; |
144 return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; |
121 end |
145 end |
122 |
146 |
123 local password, state; |
147 local SaltedPassword = self.state.salted_password; |
124 if self.profile.plain then |
148 local ClientKey = HMAC_f(SaltedPassword, "Client Key") |
125 password, state = self.profile.plain(self.state.name, self.realm) |
149 local ServerKey = HMAC_f(SaltedPassword, "Server Key") |
126 if state == nil then return "failure", "not-authorized" |
150 local StoredKey = H_f(ClientKey) |
127 elseif state == false then return "failure", "account-disabled" end |
151 local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") |
128 password = saslprep(password); |
152 local ClientSignature = HMAC_f(StoredKey, AuthMessage) |
129 if not password then |
153 local ClientProof = binaryXOR(ClientKey, ClientSignature) |
130 log("debug", "Password violates SASLprep."); |
154 local ServerSignature = HMAC_f(ServerKey, AuthMessage) |
131 return "failure", "not-authorized", "Invalid password." |
155 |
|
156 if base64.encode(ClientProof) == self.state.proof then |
|
157 local server_final_message = "v="..base64.encode(ServerSignature); |
|
158 self["username"] = self.state.name; |
|
159 return "success", server_final_message; |
|
160 else |
|
161 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; |
132 end |
162 end |
133 end |
163 end |
134 |
|
135 local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i) |
|
136 local ClientKey = hmac_sha1(SaltedPassword, "Client Key") |
|
137 local ServerKey = hmac_sha1(SaltedPassword, "Server Key") |
|
138 local StoredKey = sha1(ClientKey) |
|
139 local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") |
|
140 local ClientSignature = hmac_sha1(StoredKey, AuthMessage) |
|
141 local ClientProof = binaryXOR(ClientKey, ClientSignature) |
|
142 local ServerSignature = hmac_sha1(ServerKey, AuthMessage) |
|
143 |
|
144 if base64.encode(ClientProof) == self.state.proof then |
|
145 local server_final_message = "v="..base64.encode(ServerSignature); |
|
146 self["username"] = self.state.name; |
|
147 return "success", server_final_message; |
|
148 else |
|
149 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; |
|
150 end |
|
151 end |
164 end |
|
165 return scram_hash; |
152 end |
166 end |
153 |
167 |
154 function init(registerMechanism) |
168 function init(registerMechanism) |
155 registerMechanism("SCRAM-SHA-1", {"plain"}, scram_sha_1); |
169 local function registerSCRAMMechanism(hash_name, hash, hmac_hash) |
|
170 registerMechanism("SCRAM-"..hash_name, {"plain", "scram-"..(hash_name:lower())}, scram_gen(hash_name:lower(), hash, hmac_hash)); |
|
171 end |
|
172 |
|
173 registerSCRAMMechanism("SHA-1", sha1, hmac_sha1); |
156 end |
174 end |
157 |
175 |
158 return _M; |
176 return _M; |