Thu, 18 Sep 2014 20:33:40 +0200
util.sasl.scram: Fix typo
355 | 1 | |
2 | local base64, unbase64 = require "mime".b64, require"mime".unb64; | |
3 | local crypto = require"crypto"; | |
4 | local bit = require"bit"; | |
5 | ||
6 | local tonumber = tonumber; | |
7 | local char, byte = string.char, string.byte; | |
8 | local gsub = string.gsub; | |
9 | local xor = bit.bxor; | |
10 | ||
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
11 | local function XOR(a, b) |
355 | 12 | return (gsub(a, "()(.)", function(i, c) |
13 | return char(xor(byte(c), byte(b, i))) | |
14 | end)); | |
15 | end | |
16 | ||
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
17 | local function H(str) |
355 | 18 | return crypto.digest("sha1", str, true); |
19 | end | |
20 | ||
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
21 | local function HMAC(key, str) |
355 | 22 | return crypto.hmac.digest("sha1", str, key, true); |
23 | end | |
24 | ||
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
25 | local function Hi(str, salt, i) |
355 | 26 | local U = HMAC(str, salt .. "\0\0\0\1"); |
27 | local ret = U; | |
28 | for _ = 2, i do | |
29 | U = HMAC(str, U); | |
30 | ret = XOR(ret, U); | |
31 | end | |
32 | return ret; | |
33 | end | |
34 | ||
35 | local function Normalize(str) | |
36 | return str; -- TODO | |
37 | end | |
38 | ||
39 | local function value_safe(str) | |
40 | return (gsub(str, "[,=]", { [","] = "=2C", ["="] = "=3D" })); | |
41 | end | |
42 | ||
43 | local function scram(stream, name) | |
44 | local username = "n=" .. value_safe(stream.username); | |
45 | local c_nonce = base64(crypto.rand.bytes(15)); | |
46 | local nonce = "r=" .. c_nonce; | |
47 | local client_first_message_bare = username .. "," .. nonce; | |
48 | local cbind_data = ""; | |
356
f95e797895ee
SCRAM: Add channel binding support (SCRAM-SHA-1-PLUS)
Kim Alvefur <zash@zash.se>
parents:
355
diff
changeset
|
49 | local gs2_cbind_flag = "y"; |
f95e797895ee
SCRAM: Add channel binding support (SCRAM-SHA-1-PLUS)
Kim Alvefur <zash@zash.se>
parents:
355
diff
changeset
|
50 | if name == "SCRAM-SHA-1-PLUS" then |
f95e797895ee
SCRAM: Add channel binding support (SCRAM-SHA-1-PLUS)
Kim Alvefur <zash@zash.se>
parents:
355
diff
changeset
|
51 | cbind_data = stream.conn:socket():getfinished(); |
f95e797895ee
SCRAM: Add channel binding support (SCRAM-SHA-1-PLUS)
Kim Alvefur <zash@zash.se>
parents:
355
diff
changeset
|
52 | gs2_cbind_flag = "p=tls-unique"; |
f95e797895ee
SCRAM: Add channel binding support (SCRAM-SHA-1-PLUS)
Kim Alvefur <zash@zash.se>
parents:
355
diff
changeset
|
53 | end |
355 | 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 | ||
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
97 | return function (stream, name) |
355 | 98 | if stream.username and (stream.password or (stream.client_key or stream.server_key)) then |
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
99 | if name == "SCRAM-SHA-1" then |
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
100 | return scram, 99; |
359 | 101 | elseif name == "SCRAM-SHA-1-PLUS" then |
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
102 | local sock = stream.conn:ssl() and stream.conn:socket(); |
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
103 | if sock and sock.getfinished then |
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
104 | return scram, 100; |
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
105 | end |
356
f95e797895ee
SCRAM: Add channel binding support (SCRAM-SHA-1-PLUS)
Kim Alvefur <zash@zash.se>
parents:
355
diff
changeset
|
106 | end |
355 | 107 | end |
108 | end | |
358
a8f6fd6a70ed
plugins.sasl: Alter mechanism loading and pass name of loaded mechanism. Fixes attempting SCRAM-PLUS when only SCRAM is offered
Kim Alvefur <zash@zash.se>
parents:
356
diff
changeset
|
109 |