|
1 |
|
2 local base64, unbase64 = require "mime".b64, require"mime".unb64; |
|
3 local crypto = require"crypto"; |
|
4 local bit = require"bit"; |
|
5 |
|
6 local XOR, H, HMAC, Hi; |
|
7 local tonumber = tonumber; |
|
8 local char, byte = string.char, string.byte; |
|
9 local gsub = string.gsub; |
|
10 local xor = bit.bxor; |
|
11 |
|
12 function XOR(a, b) |
|
13 return (gsub(a, "()(.)", function(i, c) |
|
14 return char(xor(byte(c), byte(b, i))) |
|
15 end)); |
|
16 end |
|
17 |
|
18 function H(str) |
|
19 return crypto.digest("sha1", str, true); |
|
20 end |
|
21 |
|
22 function HMAC(key, str) |
|
23 return crypto.hmac.digest("sha1", str, key, true); |
|
24 end |
|
25 |
|
26 function Hi(str, salt, i) |
|
27 local U = HMAC(str, salt .. "\0\0\0\1"); |
|
28 local ret = U; |
|
29 for _ = 2, i do |
|
30 U = HMAC(str, U); |
|
31 ret = XOR(ret, U); |
|
32 end |
|
33 return ret; |
|
34 end |
|
35 |
|
36 -- assert(Hi("password", "salt", 1) == string.char(0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06, 0x2f, 0xe0, 0x37, 0xa6)); |
|
37 -- assert(Hi("password", "salt", 2) == string.char(0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c, 0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0, 0xd8, 0xde, 0x89, 0x57)); |
|
38 |
|
39 local function Normalize(str) |
|
40 return str; -- TODO |
|
41 end |
|
42 |
|
43 local function value_safe(str) |
|
44 return (gsub(str, "[,=]", { [","] = "=2C", ["="] = "=3D" })); |
|
45 end |
|
46 |
|
47 local function scram(stream, name) |
|
48 local username = "n=" .. value_safe(stream.username); |
|
49 local c_nonce = base64(crypto.rand.bytes(15)); |
|
50 local nonce = "r=" .. c_nonce; |
|
51 local client_first_message_bare = username .. "," .. nonce; |
|
52 local cbind_data = ""; |
|
53 local gs2_cbind_flag = "n" -- TODO channel binding |
|
54 local gs2_header = gs2_cbind_flag .. ",,"; |
|
55 local client_first_message = gs2_header .. client_first_message_bare; |
|
56 local cont, server_first_message = coroutine.yield(client_first_message); |
|
57 if cont ~= "challenge" then return false end |
|
58 |
|
59 local salt, iteration_count; |
|
60 nonce, salt, iteration_count = server_first_message:match("(r=[^,]+),s=([^,]*),i=(%d+)"); |
|
61 local i = tonumber(iteration_count); |
|
62 salt = unbase64(salt); |
|
63 if not nonce or not salt or not i then |
|
64 return false, "Could not parse server_first_message"; |
|
65 elseif nonce:find(c_nonce, 3, true) ~= 3 then |
|
66 return false, "nonce sent by server does not match our nonce"; |
|
67 elseif nonce == c_nonce then |
|
68 return false, "server did not append s-nonce to nonce"; |
|
69 end |
|
70 |
|
71 local cbind_input = gs2_header .. cbind_data; |
|
72 local channel_binding = "c=" .. base64(cbind_input); |
|
73 local client_final_message_without_proof = channel_binding .. "," .. nonce; |
|
74 |
|
75 local SaltedPassword = Hi(Normalize(stream.password), salt, i); |
|
76 local ClientKey = HMAC(SaltedPassword, "Client Key"); |
|
77 local StoredKey = H(ClientKey); |
|
78 local AuthMessage = client_first_message_bare .. "," .. server_first_message .. "," .. client_final_message_without_proof; |
|
79 local ClientSignature = HMAC(StoredKey, AuthMessage); |
|
80 local ClientProof = XOR(ClientKey, ClientSignature); |
|
81 local ServerKey = HMAC(SaltedPassword, "Server Key"); |
|
82 local ServerSignature = HMAC(ServerKey, AuthMessage); |
|
83 |
|
84 local proof = "p=" .. base64(ClientProof); |
|
85 local client_final_message = client_final_message_without_proof .. "," .. proof; |
|
86 |
|
87 local ok, server_final_message = coroutine.yield(client_final_message); |
|
88 if ok ~= "success" then return false, "success-expected" end |
|
89 |
|
90 local verifier = server_final_message:match("v=([^,]+)"); |
|
91 if unbase64(verifier) ~= ServerSignature then |
|
92 return false, "server signature did not match"; |
|
93 end |
|
94 return true; |
|
95 end |
|
96 |
|
97 return function (stream, mechanisms, preference, supported) |
|
98 if stream.username and (stream.password or (stream.client_key or stream.server_key)) then |
|
99 mechanisms["SCRAM-SHA-1"] = scram; |
|
100 preference["SCRAM-SHA-1"] = 99; |
|
101 -- TODO SCRAM-SHA-1-PLUS |
|
102 end |
|
103 end |