util/sasl/scram.lua

changeset 2194
d18b4d22b8da
parent 2193
1509cabb8321
child 2195
08a6b91bfe7b
equal deleted inserted replaced
2193:1509cabb8321 2194:d18b4d22b8da
10 -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 -- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11 -- 11 --
12 -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 13
14 local s_match = string.match; 14 local s_match = string.match;
15 15 local type = type
16 local string = string
16 local base64 = require "util.encodings".base64; 17 local base64 = require "util.encodings".base64;
17 local xor = require "bit".bxor 18 local xor = require "bit".bxor
18 local hmac_sha1 = require "util.hmac".sha1; 19 local hmac_sha1 = require "util.hmac".sha1;
19 local sha1 = require "util.hashes".sha1; 20 local sha1 = require "util.hashes".sha1;
20 local generate_uuid = require "util.uuid".generate; 21 local generate_uuid = require "util.uuid".generate;
32 end 33 end
33 return result 34 return result
34 end 35 end
35 36
36 local function binaryXOR( a, b ) 37 local function binaryXOR( a, b )
37 if string.len(a) > string.len(b) then 38 if a:len() > b:len() then
38 b = string.rep("\0", a:len() - b:len())..b 39 b = string.rep("\0", a:len() - b:len())..b
39 elseif string.len(a) < string.len(b) then 40 elseif string.len(a) < string.len(b) then
40 a = string.rep("\0", b:len() - a:len())..a 41 a = string.rep("\0", b:len() - a:len())..a
41 end 42 end
42 local result = "" 43 local result = ""
58 return res 59 return res
59 end 60 end
60 61
61 local function validate_username(username) 62 local function validate_username(username)
62 -- check for forbidden char sequences 63 -- check for forbidden char sequences
63 for eq in s:gmatch("=(.?.?)") do 64 for eq in username:gmatch("=(.?.?)") do
64 if eq ~= "2D" and eq ~= "3D" then return false end end return true; 65 if eq ~= "2D" and eq ~= "3D" then
66 return false
67 end
68 end
69
65 -- replace =2D with , and =3D with = 70 -- replace =2D with , and =3D with =
66 71
67 -- apply SASLprep 72 -- apply SASLprep
68 return username; 73 return username;
69 end 74 end
72 if not self.state then self["state"] = {} end 77 if not self.state then self["state"] = {} end
73 78
74 if not self.state.name then 79 if not self.state.name then
75 -- we are processing client_first_message 80 -- we are processing client_first_message
76 local client_first_message = message; 81 local client_first_message = message;
82 self.state["client_first_message"] = client_first_message;
77 self.state["name"] = client_first_message:match("n=(.+),r=") 83 self.state["name"] = client_first_message:match("n=(.+),r=")
78 self.state["clientnonce"] = client_first_message:match("r=([^,]+)") 84 self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
79 85
80 self.state.name = validate_username(self.state.name); 86 self.state.name = validate_username(self.state.name);
81 if not self.state.name or not self.state.clientnonce then 87 if not self.state.name or not self.state.clientnonce then
83 end 89 end
84 self.state["servernonce"] = generate_uuid(); 90 self.state["servernonce"] = generate_uuid();
85 self.state["salt"] = generate_uuid(); 91 self.state["salt"] = generate_uuid();
86 92
87 local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i; 93 local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i;
94 self.state["server_first_message"] = server_first_message;
88 return "challenge", server_first_message 95 return "challenge", server_first_message
89 else 96 else
97 if type(message) ~= "string" then return "failure", "malformed-request" end
90 -- we are processing client_final_message 98 -- we are processing client_final_message
91 local client_final_message = message; 99 local client_final_message = message;
92 100
93 self.state["proof"] = client_final_message:match("p=(.+)"); 101 self.state["proof"] = client_final_message:match("p=(.+)");
94 self.state["nonce"] = client_final_message:match("r=(.+),p="); 102 self.state["nonce"] = client_final_message:match("r=(.+),p=");
106 114
107 local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i) 115 local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i)
108 local ClientKey = hmac_sha1(SaltedPassword, "Client Key") 116 local ClientKey = hmac_sha1(SaltedPassword, "Client Key")
109 local ServerKey = hmac_sha1(SaltedPassword, "Server Key") 117 local ServerKey = hmac_sha1(SaltedPassword, "Server Key")
110 local StoredKey = sha1(ClientKey) 118 local StoredKey = sha1(ClientKey)
111 local AuthMessage = "n=" .. s_match(client_first_message,"n=(.+)") .. "," .. server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") 119 local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
112 local ClientSignature = hmac_sha1(StoredKey, AuthMessage) 120 local ClientSignature = hmac_sha1(StoredKey, AuthMessage)
113 local ClientProof = binaryXOR(ClientKey, ClientSignature) 121 local ClientProof = binaryXOR(ClientKey, ClientSignature)
114 local ServerSignature = hmac_sha1(ServerKey, AuthMessage) 122 local ServerSignature = hmac_sha1(ServerKey, AuthMessage)
115 123
116 if base64.encode(ClientProof) == self.state.proof then 124 if base64.encode(ClientProof) == self.state.proof then
117 local server_final_message = "v="..base64.encode(ServerSignature); 125 local server_final_message = "v="..base64.encode(ServerSignature);
126 self["username"] = self.state.name;
118 return "success", server_final_message; 127 return "success", server_final_message;
119 else 128 else
120 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; 129 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated.";
121 end 130 end
122 end 131 end

mercurial