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