Comparison

util/sasl/scram.lua @ 5868:bc37c6758f3a

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