Comparison

util/sasl/scram.lua @ 2990:21933063dd9f

util.sasl: Abstracting out the hash function used since SCRAM is independent of it. Adding scram-{mech} authentication backend support.
author Tobias Markmann <tm@ayena.de>
date Sun, 28 Feb 2010 22:23:03 +0100
parent 2648:538dd5f1810a
child 2993:06d06fdd190b
comparison
equal deleted inserted replaced
2717:c5dd53b38c2f 2990:21933063dd9f
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;