Software /
code /
prosody
Comparison
util/sasl/scram.lua @ 5873:4669bd7038c7
Merge 0.10->trunk
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Tue, 15 Oct 2013 01:38:02 +0200 |
parent | 5871:e80916ce8d32 |
child | 6786:3deeb38d79ab |
comparison
equal
deleted
inserted
replaced
5866:10a16fdebe2c | 5873:4669bd7038c7 |
---|---|
99 local server_key = hmac_sha1(salted_password, "Server Key"); | 99 local server_key = hmac_sha1(salted_password, "Server Key"); |
100 return true, stored_key, server_key | 100 return true, stored_key, server_key |
101 end | 101 end |
102 | 102 |
103 local function scram_gen(hash_name, H_f, HMAC_f) | 103 local function scram_gen(hash_name, H_f, HMAC_f) |
104 local profile_name = "scram_" .. hashprep(hash_name); | |
104 local function scram_hash(self, message) | 105 local function scram_hash(self, message) |
105 if not self.state then self["state"] = {} end | |
106 local support_channel_binding = false; | 106 local support_channel_binding = false; |
107 if self.profile.cb then support_channel_binding = true; end | 107 if self.profile.cb then support_channel_binding = true; end |
108 | 108 |
109 if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end | 109 if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end |
110 if not self.state.name then | 110 local state = self.state; |
111 if not state then | |
111 -- we are processing client_first_message | 112 -- we are processing client_first_message |
112 local client_first_message = message; | 113 local client_first_message = message; |
113 | 114 |
114 -- TODO: fail if authzid is provided, since we don't support them yet | 115 -- TODO: fail if authzid is provided, since we don't support them yet |
115 self.state["client_first_message"] = client_first_message; | 116 local gs2_header, gs2_cbind_flag, gs2_cbind_name, authzid, client_first_message_bare, username, clientnonce |
116 self.state["gs2_cbind_flag"], self.state["gs2_cbind_name"], self.state["authzid"], self.state["name"], self.state["clientnonce"] | 117 = s_match(client_first_message, "^(([pny])=?([^,]*),([^,]*),)(m?=?[^,]*,?n=([^,]*),r=([^,]*),?.*)$"); |
117 = client_first_message:match("^([ynp])=?([%a%-]*),(.*),n=(.*),r=([^,]*).*"); | |
118 | |
119 local gs2_cbind_flag = self.state.gs2_cbind_flag; | |
120 | 118 |
121 if not gs2_cbind_flag then | 119 if not gs2_cbind_flag then |
122 return "failure", "malformed-request"; | 120 return "failure", "malformed-request"; |
123 end | 121 end |
124 | 122 |
133 support_channel_binding = false; | 131 support_channel_binding = false; |
134 end | 132 end |
135 | 133 |
136 if support_channel_binding and gs2_cbind_flag == "p" then | 134 if support_channel_binding and gs2_cbind_flag == "p" then |
137 -- check whether we support the proposed channel binding type | 135 -- check whether we support the proposed channel binding type |
138 if not self.profile.cb[self.state.gs2_cbind_name] then | 136 if not self.profile.cb[gs2_cbind_name] then |
139 return "failure", "malformed-request", "Proposed channel binding type isn't supported."; | 137 return "failure", "malformed-request", "Proposed channel binding type isn't supported."; |
140 end | 138 end |
141 else | 139 else |
142 -- no channel binding, | 140 -- no channel binding, |
143 self.state.gs2_cbind_name = nil; | 141 gs2_cbind_name = nil; |
144 end | 142 end |
145 | 143 |
146 if not self.state.name or not self.state.clientnonce then | 144 username = validate_username(username, self.profile.nodeprep); |
147 return "failure", "malformed-request", "Channel binding isn't support at this time."; | 145 if not username then |
148 end | |
149 | |
150 self.state.name = validate_username(self.state.name, self.profile.nodeprep); | |
151 if not self.state.name then | |
152 log("debug", "Username violates either SASLprep or contains forbidden character sequences.") | 146 log("debug", "Username violates either SASLprep or contains forbidden character sequences.") |
153 return "failure", "malformed-request", "Invalid username."; | 147 return "failure", "malformed-request", "Invalid username."; |
154 end | 148 end |
155 | 149 |
156 self.state["servernonce"] = generate_uuid(); | |
157 | |
158 -- retreive credentials | 150 -- retreive credentials |
151 local stored_key, server_key, salt, iteration_count; | |
159 if self.profile.plain then | 152 if self.profile.plain then |
160 local password, state = self.profile.plain(self, self.state.name, self.realm) | 153 local password, state = self.profile.plain(self, username, self.realm) |
161 if state == nil then return "failure", "not-authorized" | 154 if state == nil then return "failure", "not-authorized" |
162 elseif state == false then return "failure", "account-disabled" end | 155 elseif state == false then return "failure", "account-disabled" end |
163 | 156 |
164 password = saslprep(password); | 157 password = saslprep(password); |
165 if not password then | 158 if not password then |
166 log("debug", "Password violates SASLprep."); | 159 log("debug", "Password violates SASLprep."); |
167 return "failure", "not-authorized", "Invalid password." | 160 return "failure", "not-authorized", "Invalid password." |
168 end | 161 end |
169 | 162 |
170 self.state.salt = generate_uuid(); | 163 salt = generate_uuid(); |
171 self.state.iteration_count = default_i; | 164 iteration_count = default_i; |
172 | 165 |
173 local succ = false; | 166 local succ = false; |
174 succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count); | 167 succ, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count); |
175 if not succ then | 168 if not succ then |
176 log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key); | 169 log("error", "Generating authentication database failed. Reason: %s", stored_key); |
177 return "failure", "temporary-auth-failure"; | 170 return "failure", "temporary-auth-failure"; |
178 end | 171 end |
179 elseif self.profile["scram_"..hashprep(hash_name)] then | 172 elseif self.profile[profile_name] then |
180 local stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self, self.state.name, self.realm); | 173 local state; |
174 stored_key, server_key, iteration_count, salt, state = self.profile[profile_name](self, username, self.realm); | |
181 if state == nil then return "failure", "not-authorized" | 175 if state == nil then return "failure", "not-authorized" |
182 elseif state == false then return "failure", "account-disabled" end | 176 elseif state == false then return "failure", "account-disabled" end |
183 | 177 end |
184 self.state.stored_key = stored_key; | 178 |
185 self.state.server_key = server_key; | 179 local nonce = clientnonce .. generate_uuid(); |
186 self.state.iteration_count = iteration_count; | 180 local server_first_message = "r="..nonce..",s="..base64.encode(salt)..",i="..iteration_count; |
187 self.state.salt = salt | 181 self.state = { |
188 end | 182 gs2_header = gs2_header; |
189 | 183 gs2_cbind_name = gs2_cbind_name; |
190 local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count; | 184 username = username; |
191 self.state["server_first_message"] = server_first_message; | 185 nonce = nonce; |
186 | |
187 server_key = server_key; | |
188 stored_key = stored_key; | |
189 client_first_message_bare = client_first_message_bare; | |
190 server_first_message = server_first_message; | |
191 } | |
192 return "challenge", server_first_message | 192 return "challenge", server_first_message |
193 else | 193 else |
194 -- we are processing client_final_message | 194 -- we are processing client_final_message |
195 local client_final_message = message; | 195 local client_final_message = message; |
196 | 196 |
197 self.state["channelbinding"], self.state["nonce"], self.state["proof"] = client_final_message:match("^c=(.*),r=(.*),.*p=(.*)"); | 197 local client_final_message_without_proof, channelbinding, nonce, proof |
198 | 198 = s_match(client_final_message, "(c=([^,]*),r=([^,]*),?.-),p=(.*)$"); |
199 if not self.state.proof or not self.state.nonce or not self.state.channelbinding then | 199 |
200 if not proof or not nonce or not channelbinding then | |
200 return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; | 201 return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; |
201 end | 202 end |
202 | 203 |
203 if self.state.gs2_cbind_name then | 204 local client_gs2_header = base64.decode(channelbinding) |
205 local our_client_gs2_header = state["gs2_header"] | |
206 if state.gs2_cbind_name then | |
204 -- we support channelbinding, so check if the value is valid | 207 -- we support channelbinding, so check if the value is valid |
205 local client_gs2_header = base64.decode(self.state.channelbinding) | 208 our_client_gs2_header = our_client_gs2_header .. self.profile.cb[state.gs2_cbind_name](self); |
206 local our_client_gs2_header = "p="..self.state.gs2_cbind_name..","..self.state["authzid"]..","..self.profile.cb[self.state.gs2_cbind_name](self); | 209 end |
207 | 210 if client_gs2_header ~= our_client_gs2_header then |
208 if client_gs2_header ~= our_client_gs2_header then | 211 return "failure", "malformed-request", "Invalid channel binding value."; |
209 return "failure", "malformed-request", "Invalid channel binding value."; | 212 end |
210 end | 213 |
211 end | 214 if nonce ~= state.nonce then |
212 | |
213 if self.state.nonce ~= self.state.clientnonce..self.state.servernonce then | |
214 return "failure", "malformed-request", "Wrong nonce in client-final-message."; | 215 return "failure", "malformed-request", "Wrong nonce in client-final-message."; |
215 end | 216 end |
216 | 217 |
217 local ServerKey = self.state.server_key; | 218 local ServerKey = state.server_key; |
218 local StoredKey = self.state.stored_key; | 219 local StoredKey = state.stored_key; |
219 | 220 |
220 local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") | 221 local AuthMessage = state.client_first_message_bare .. "," .. state.server_first_message .. "," .. client_final_message_without_proof |
221 local ClientSignature = HMAC_f(StoredKey, AuthMessage) | 222 local ClientSignature = HMAC_f(StoredKey, AuthMessage) |
222 local ClientKey = binaryXOR(ClientSignature, base64.decode(self.state.proof)) | 223 local ClientKey = binaryXOR(ClientSignature, base64.decode(proof)) |
223 local ServerSignature = HMAC_f(ServerKey, AuthMessage) | 224 local ServerSignature = HMAC_f(ServerKey, AuthMessage) |
224 | 225 |
225 if StoredKey == H_f(ClientKey) then | 226 if StoredKey == H_f(ClientKey) then |
226 local server_final_message = "v="..base64.encode(ServerSignature); | 227 local server_final_message = "v="..base64.encode(ServerSignature); |
227 self["username"] = self.state.name; | 228 self["username"] = state.username; |
228 return "success", server_final_message; | 229 return "success", server_final_message; |
229 else | 230 else |
230 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; | 231 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; |
231 end | 232 end |
232 end | 233 end |