function dbmethods:auth_scram_sha1(username, password) |
| local user = string.gsub(string.gsub(username, '=', '=3D'), ',' , '=2C') |
| local nonce = ngx.encode_base64(string.sub(tostring(math.random()), 3 , 14)) |
| local first_bare = "n=" .. user .. ",r=" .. nonce |
| local sasl_start_payload = ngx.encode_base64("n,," .. first_bare) |
| |
| r, err = self:cmd(attachpairs_start({ |
| saslStart = 1 ; |
| mechanism = "SCRAM-SHA-1" ; |
| autoAuthorize = 1 ; |
| payload = sasl_start_payload ; |
| } , "saslStart" ) ) |
| if not r then |
| return nil, err |
| end |
| |
| local conversationId = r['conversationId'] |
| local server_first = r['payload'] |
| local parsed_s = ngx.decode_base64(server_first) |
| local parsed_t = {} |
| for k, v in string.gmatch(parsed_s, "(%w+)=([^,]*)") do |
| parsed_t[k] = v |
| end |
| local iterations = tonumber(parsed_t['i']) |
| local salt = parsed_t['s'] |
| local rnonce = parsed_t['r'] |
|
|
| if not string.sub(rnonce, 1, 12) == nonce then |
| return nil, 'Server returned an invalid nonce.' |
| end |
| local without_proof = "c=biws,r=" .. rnonce |
| local pbkdf2_key = pass_digest ( username , password ) |
| local salted_pass = pbkdf2_hmac_sha1(pbkdf2_key, iterations, ngx.decode_base64(salt), 20) |
| local client_key = ngx.hmac_sha1(salted_pass, "Client Key") |
| local stored_key = ngx.sha1_bin(client_key) |
| local auth_msg = first_bare .. ',' .. parsed_s .. ',' .. without_proof |
| local client_sig = ngx.hmac_sha1(stored_key, auth_msg) |
| local client_key_xor_sig = xor_bytestr(client_key, client_sig) |
| local client_proof = "p=" .. ngx.encode_base64(client_key_xor_sig) |
| local client_final = ngx.encode_base64(without_proof .. ',' .. client_proof) |
| local server_key = ngx.hmac_sha1(salted_pass, "Server Key") |
| local server_sig = ngx.encode_base64(ngx.hmac_sha1(server_key, auth_msg)) |
| |
| r, err = self:cmd(attachpairs_start({ |
| saslContinue = 1 ; |
| conversationId = conversationId ; |
| payload = client_final ; |
| } , "saslContinue" ) ) |
| if not r then |
| return nil, err |
| end |
| parsed_s = ngx.decode_base64(r['payload']) |
| parsed_t = {} |
| for k, v in string.gmatch(parsed_s, "(%w+)=([^,]*)") do |
| parsed_t[k] = v |
| end |
| if parsed_t['v'] ~= server_sig then |
| return nil, "Server returned an invalid signature." |
| end |
| |
| if not r['done'] then |
| r, err = self:cmd(attachpairs_start({ |
| saslContinue = 1 ; |
| conversationId = conversationId ; |
| payload = ngx.encode_base64("") ; |
| } , "saslContinue" ) ) |
| if not r then |
| return nil, err |
| end |
| if not r['done'] then |
| return nil, 'SASL conversation failed to complete.' |
| end |
| return 1 |
| end |
| return 1 |
| end |