Software /
code /
prosody
Comparison
util/sasl.lua @ 276:30893439d5d1
Some early attempts on DIGEST-MD5.
author | Tobias Markmann <tm@ayena.de> |
---|---|
date | Sun, 09 Nov 2008 21:16:57 +0100 |
parent | 50:56272224ca4c |
child | 277:00c2fc751f50 |
comparison
equal
deleted
inserted
replaced
230:e46525f5b2a4 | 276:30893439d5d1 |
---|---|
1 | 1 |
2 local base64 = require "base64" | 2 local base64 = require "base64" |
3 local md5 = require "md5" | |
4 local crypto = require "crypto" | |
3 local log = require "util.logger".init("sasl"); | 5 local log = require "util.logger".init("sasl"); |
4 local tostring = tostring; | 6 local tostring = tostring; |
5 local st = require "util.stanza"; | 7 local st = require "util.stanza"; |
8 local generate_uuid = require "util.uuid".generate; | |
6 local s_match = string.match; | 9 local s_match = string.match; |
10 local math = require "math" | |
11 local type = type | |
12 local error = error | |
13 local print = print | |
14 | |
7 module "sasl" | 15 module "sasl" |
8 | |
9 | 16 |
10 local function new_plain(onAuth, onSuccess, onFail, onWrite) | 17 local function new_plain(onAuth, onSuccess, onFail, onWrite) |
11 local object = { mechanism = "PLAIN", onAuth = onAuth, onSuccess = onSuccess, onFail = onFail, | 18 local object = { mechanism = "PLAIN", onAuth = onAuth, onSuccess = onSuccess, onFail = onFail, |
12 onWrite = onWrite} | 19 onWrite = onWrite} |
13 --local challenge = base64.encode(""); | 20 local challenge = base64.encode(""); |
14 --onWrite(st.stanza("challenge", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):text(challenge)) | 21 --onWrite(st.stanza("challenge", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):text(challenge)) |
15 object.feed = function(self, stanza) | 22 object.feed = function(self, stanza) |
16 if stanza.name ~= "response" and stanza.name ~= "auth" then self.onFail("invalid-stanza-tag") end | 23 if stanza.name ~= "response" and stanza.name ~= "auth" then self.onFail("invalid-stanza-tag") end |
17 if stanza.attr.xmlns ~= "urn:ietf:params:xml:ns:xmpp-sasl" then self.onFail("invalid-stanza-namespace") end | 24 if stanza.attr.xmlns ~= "urn:ietf:params:xml:ns:xmpp-sasl" then self.onFail("invalid-stanza-namespace") end |
18 local response = base64.decode(stanza[1]) | 25 local response = base64.decode(stanza[1]) |
28 end | 35 end |
29 return object | 36 return object |
30 end | 37 end |
31 | 38 |
32 | 39 |
40 --[[ | |
41 SERVER: | |
42 nonce="3145176401",qop="auth",charset=utf-8,algorithm=md5-sess | |
43 | |
44 CLIENT: username="tobiasfar",nonce="3145176401",cnonce="pJiW7hzeZLvOSAf7gBzwTzLWe4obYOVDlnNESzQCzGg=",nc=00000001,digest-uri="xmpp/jabber.org",qop=auth,response=99a93ba75235136e6403c3a2ba37089d,charset=utf-8 | |
45 | |
46 username="tobias",nonce="4406697386",cnonce="wUnT7vYrOB0V8D/lKd5bhpaNCk+hLJwc8T4CBCqp7WM=",nc=00000001,digest-uri="xmpp/luaetta.ath.cx",qop=auth,response=d202b8a1bdf8204816fb23c5f87b6b63,charset=utf-8 | |
47 | |
48 SERVER: | |
49 rspauth=ab66d28c260e97da577ce3aac46a8991 | |
50 ]]-- | |
51 local function new_digest_md5(onAuth, onSuccess, onFail, onWrite) | |
52 local function H(s) | |
53 return md5.sum(s) | |
54 end | |
55 | |
56 local function KD(k, s) | |
57 return H(k..":"..s) | |
58 end | |
59 | |
60 local function HEX(n) | |
61 return md5.sumhexa(n) | |
62 end | |
63 | |
64 local function HMAC(k, s) | |
65 return crypto.hmac.digest("md5", s, k, true) | |
66 end | |
67 | |
68 local function serialize(message) | |
69 local data = "" | |
70 | |
71 if type(message) ~= "table" then error("serialize needs an argument of type table.") end | |
72 | |
73 -- testing all possible values | |
74 if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end | |
75 if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end | |
76 if message["charset"] then data = data..[[charset=]]..message.charset.."," end | |
77 if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end | |
78 if message["rspauth"] then data = data..[[rspauth=]]..message.algorith.."," end | |
79 data = data:gsub(",$", "") | |
80 return data | |
81 end | |
82 | |
83 local function parse(data) | |
84 message = {} | |
85 for k, v in string.gmatch(data, [[([%w%-])="?[%w%-]"?,?]]) do | |
86 message[k] = v | |
87 end | |
88 return message | |
89 end | |
90 | |
91 local object = { mechanism = "DIGEST-MD5", onAuth = onAuth, onSuccess = onSuccess, onFail = onFail, | |
92 onWrite = onWrite } | |
93 | |
94 --TODO: something better than math.random would be nice, maybe OpenSSL's random number generator | |
95 object.nonce = math.random(0, 9) | |
96 for i = 1, 9 do object.nonce = object.nonce..math.random(0, 9) end | |
97 object.step = 1 | |
98 object.nonce_count = {} | |
99 local challenge = base64.encode(serialize({ nonce = object.nonce, | |
100 qop = "auth", | |
101 charset = "utf-8", | |
102 algorithm = "md5-sess"} )); | |
103 object.onWrite(st.stanza("challenge", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):text(challenge)) | |
104 object.feed = function(self, stanza) | |
105 print(tostring(stanza)) | |
106 if stanza.name ~= "response" and stanza.name ~= "auth" then self.onFail("invalid-stanza-tag") end | |
107 if stanza.attr.xmlns ~= "urn:ietf:params:xml:ns:xmpp-sasl" then self.onFail("invalid-stanza-namespace") end | |
108 if stanza.name == "auth" then return end | |
109 self.step = self.step + 1 | |
110 if (self.step == 2) then | |
111 | |
112 log("debug", tostring(stanza[1])) | |
113 local response = parse(base64.decode(stanza[1])) | |
114 -- check for replay attack | |
115 if response["nonce-count"] then | |
116 if self.nonce_count[response["nonce-count"]] then self.onFail("not-authorized") end | |
117 end | |
118 | |
119 -- check for username, it's REQUIRED by RFC 2831 | |
120 if not response["username"] then | |
121 self.onFail("malformed-request") | |
122 end | |
123 | |
124 -- check for nonce, ... | |
125 if not response["nonce"] then | |
126 self.onFail("malformed-request") | |
127 else | |
128 -- check if it's the right nonce | |
129 if response["nonce"] ~= self.nonce then self.onFail("malformed-request") end | |
130 end | |
131 | |
132 if not response["cnonce"] then self.onFail("malformed-request") end | |
133 if not response["qop"] then response["qop"] = "auth" end | |
134 | |
135 local hostname = "" | |
136 if response["digest-uri"] then | |
137 local uri = response["digest-uri"]:gmatch("^(%w)/(%w)") | |
138 local protocol = uri[1] | |
139 log(protocol) | |
140 local hostname = uri[2] | |
141 log(hostname) | |
142 end | |
143 | |
144 -- compare response_value with own calculation | |
145 local A1-- = H(response["username"]..":"..realm-value, ":", passwd } ), | |
146 -- ":", nonce-value, ":", cnonce-value) | |
147 local A2 | |
148 | |
149 local response_value = HEX(KD(HEX(H(A1)), response["nonce"]..":"..response["nonce-count"]..":"..response["cnonce-value"]..":"..response["qop"]..":"..HEX(H(A2)))) | |
150 | |
151 if response["qop"] == "auth" then | |
152 | |
153 else | |
154 | |
155 end | |
156 | |
157 local response_value = HEX(KD(HEX(H(A1)), response["nonce"]..":"..response["nonce-count"]..":"..response["cnonce-value"]..":"..response["qop"]..":"..HEX(H(A2)))) | |
158 | |
159 end | |
160 --[[ | |
161 local authorization = s_match(response, "([^&%z]+)") | |
162 local authentication = s_match(response, "%z([^&%z]+)%z") | |
163 local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") | |
164 if self.onAuth(authentication, password) == true then | |
165 self.onWrite(st.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"})) | |
166 self.onSuccess(authentication) | |
167 else | |
168 self.onWrite(st.stanza("failure", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):tag("temporary-auth-failure")); | |
169 end]]-- | |
170 end | |
171 return object | |
172 end | |
173 | |
33 function new(mechanism, onAuth, onSuccess, onFail, onWrite) | 174 function new(mechanism, onAuth, onSuccess, onFail, onWrite) |
34 local object | 175 local object |
35 if mechanism == "PLAIN" then object = new_plain(onAuth, onSuccess, onFail, onWrite) | 176 if mechanism == "PLAIN" then object = new_plain(onAuth, onSuccess, onFail, onWrite) |
177 elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(onAuth, onSuccess, onFail, onWrite) | |
36 else | 178 else |
37 log("debug", "Unsupported SASL mechanism: "..tostring(mechanism)); | 179 log("debug", "Unsupported SASL mechanism: "..tostring(mechanism)); |
38 onFail("unsupported-mechanism") | 180 onFail("unsupported-mechanism") |
39 end | 181 end |
40 return object | 182 return object |