Software /
code /
prosody-modules
Comparison
mod_s2s_auth_posh/mod_s2s_auth_posh.lua @ 3199:cb7c24305ed2
mod_s2s_auth_posh: Changes done outside of version control during 2014-2017
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Wed, 15 Mar 2017 15:49:46 +0100 |
parent | 3198:f3e452b43cfe |
child | 3200:d070a751b6ed |
comparison
equal
deleted
inserted
replaced
3198:f3e452b43cfe | 3199:cb7c24305ed2 |
---|---|
6 -- | 6 -- |
7 module:set_global(); | 7 module:set_global(); |
8 --local https = require 'ssl.https' | 8 --local https = require 'ssl.https' |
9 --local http = require "socket.http"; | 9 --local http = require "socket.http"; |
10 local json = require 'util.json' | 10 local json = require 'util.json' |
11 local serialization = require 'util.serialization' | |
12 | 11 |
13 local nameprep = require "util.encodings".stringprep.nameprep; | |
14 local to_unicode = require "util.encodings".idna.to_unicode; | |
15 local cert_verify_identity = require "util.x509".verify_identity; | |
16 local der2pem = require"util.x509".der2pem; | |
17 local base64 = require"util.encodings".base64; | 12 local base64 = require"util.encodings".base64; |
13 local pem2der = require "util.x509".pem2der; | |
14 local hashes = require"util.hashes"; | |
15 local build_url = require"socket.url".build; | |
16 local async = require "util.async"; | |
17 local http = require"net.http"; | |
18 | |
19 local cache = require "util.cache".new(100); | |
20 | |
21 local hash_order = { "sha-512", "sha-384", "sha-256", "sha-224", "sha-1" }; | |
22 local hash_funcs = { hashes.sha512, hashes.sha384, hashes.sha256, hashes.sha224, hashes.sha1 }; | |
18 | 23 |
19 local function posh_lookup(host_session, resume) | 24 local function posh_lookup(host_session, resume) |
20 -- do nothing if posh info already exists | 25 -- do nothing if posh info already exists |
21 if host_session.posh ~= nil then return end | 26 if host_session.posh ~= nil then return end |
22 | |
23 (host_session.log or module._log)("debug", "DIRECTION: %s", tostring(host_session.direction)); | |
24 | 27 |
25 local target_host = false; | 28 local target_host = false; |
26 if host_session.direction == "incoming" then | 29 if host_session.direction == "incoming" then |
27 target_host = host_session.from_host; | 30 target_host = host_session.from_host; |
28 elseif host_session.direction == "outgoing" then | 31 elseif host_session.direction == "outgoing" then |
29 target_host = host_session.to_host; | 32 target_host = host_session.to_host; |
30 end | 33 end |
31 | 34 |
32 local url = "https://"..target_host.."/.well-known/posh._xmpp-server._tcp.json" | 35 local cached = cache:get(target_host); |
36 if cached and os.time() < cached.expires then | |
37 host_session.posh = { jwk = cached }; | |
38 return false; | |
39 end | |
40 local log = host_session.log or module._log; | |
33 | 41 |
34 (host_session.log or module._log)("debug", "Request POSH information for %s", tostring(target_host)); | 42 log("debug", "Session direction: %s", tostring(host_session.direction)); |
35 local request = http.request(url, nil, function(response, code, req) | 43 |
36 (host_session.log or module._log)("debug", "Received POSH response"); | 44 local url = build_url { scheme = "https", host = target_host, path = "/.well-known/posh/xmpp-server.json" }; |
37 local jwk = json.decode(response); | 45 |
38 if not jwk then | 46 log("debug", "Request POSH information for %s", tostring(target_host)); |
39 (host_session.log or module._log)("error", "POSH response is not valid JSON!"); | 47 http.request(url, nil, function(response, code) |
40 (host_session.log or module._log)("debug", tostring(response)); | 48 if code ~= 200 then |
41 end | 49 log("debug", "No or invalid POSH response received"); |
42 host_session.posh = {}; | 50 resume(); |
43 host_session.posh.jwk = jwk; | 51 return; |
44 resume() | 52 end |
45 end) | 53 log("debug", "Received POSH response"); |
54 local jwk = json.decode(response); | |
55 if not jwk then | |
56 log("error", "POSH response is not valid JSON!\n%s", tostring(response)); | |
57 resume(); | |
58 return; | |
59 end | |
60 host_session.posh = { orig = response }; | |
61 jwk.expires = os.time() + tonumber(jwk.expires) or 3600; | |
62 host_session.posh.jwk = jwk; | |
63 cache:set(target_host, jwk); | |
64 resume(); | |
65 end) | |
46 return true; | 66 return true; |
47 end | |
48 | |
49 function module.add_host(module) | |
50 local function on_new_s2s(event) | |
51 local host_session = event.origin; | |
52 if host_session.type == "s2sout" or host_session.type == "s2sin" or host_session.posh ~= nil then return end -- Already authenticated | |
53 | |
54 host_session.log("debug", "Pausing connection until POSH lookup is completed"); | |
55 host_session.conn:pause() | |
56 local function resume() | |
57 host_session.log("debug", "POSH lookup completed, resuming connection"); | |
58 host_session.conn:resume() | |
59 end | |
60 if not posh_lookup(host_session, resume) then | |
61 resume(); | |
62 end | |
63 end | |
64 | |
65 -- New outgoing connections | |
66 module:hook("stanza/http://etherx.jabber.org/streams:features", on_new_s2s, 501); | |
67 module:hook("s2sout-authenticate-legacy", on_new_s2s, 200); | |
68 | |
69 -- New incoming connections | |
70 module:hook("s2s-stream-features", on_new_s2s, 10); | |
71 | |
72 module:hook("s2s-authenticated", function(event) | |
73 local session = event.session; | |
74 if session.posh and not session.secure then | |
75 -- Bogus replies should trigger this path | |
76 -- How does this interact with Dialback? | |
77 session:close({ | |
78 condition = "policy-violation", | |
79 text = "Secure server-to-server communication is required but was not " | |
80 ..((session.direction == "outgoing" and "offered") or "used") | |
81 }); | |
82 return false; | |
83 end | |
84 -- Cleanup | |
85 session.posh = nil; | |
86 end); | |
87 end | 67 end |
88 | 68 |
89 -- Do POSH authentication | 69 -- Do POSH authentication |
90 module:hook("s2s-check-certificate", function(event) | 70 module:hook("s2s-check-certificate", function(event) |
91 local session, cert = event.session, event.cert; | 71 local session, cert = event.session, event.cert; |
92 (session.log or module._log)("info", "Trying POSH authentication."); | 72 local log = session.log or module._log; |
73 log("info", "Trying POSH authentication."); | |
93 -- if session.cert_identity_status ~= "valid" and session.posh then | 74 -- if session.cert_identity_status ~= "valid" and session.posh then |
94 if session.posh then | 75 local wait, done = async.waiter(); |
95 local target_host = event.host; | 76 if posh_lookup(session, done) then |
77 wait(); | |
78 end | |
79 local posh = session.posh; | |
80 local jwk = posh and posh.jwk; | |
81 local fingerprints = jwk and jwk.fingerprints; | |
96 | 82 |
97 local jwk = session.posh.jwk; | 83 local cert_der = pem2der(cert:pem()); |
98 | 84 local cert_hashes = {}; |
99 local connection_certs = session.conn:socket():getpeerchain(); | 85 for i = 1, #hash_order do |
100 | 86 cert_hashes[i] = base64.encode(hash_funcs[i](cert_der)); |
101 local x5c_table = jwk.keys[1].x5c; | 87 end |
102 | 88 for i = 1, #fingerprints do |
103 local wire_cert = connection_certs[1]; | 89 local fp = fingerprints[i]; |
104 local jwk_cert = ssl.x509.load(der2pem(base64.decode(x5c_table[1]))); | 90 for j = 1, #hash_order do |
105 | 91 local hash = fp[hash_order[j]]; |
106 if (wire_cert and jwk_cert and | 92 if cert_hashes[j] == hash then |
107 wire_cert:digest("sha1") == jwk_cert:digest("sha1")) then | 93 session.cert_chain_status = "valid"; |
108 session.cert_chain_status = "valid"; | 94 session.cert_identity_status = "valid"; |
109 session.cert_identity_status = "valid"; | 95 log("debug", "POSH authentication succeeded!"); |
110 (session.log or module._log)("debug", "POSH authentication succeeded!"); | 96 return true; |
111 return true; | 97 elseif hash then |
112 else | 98 -- Don't try weaker hashes |
113 (session.log or module._log)("debug", "POSH authentication failed!"); | 99 break; |
114 (session.log or module._log)("debug", "(top wire sha1 vs top jwk sha1) = (%s vs %s)", wire_cert:digest("sha1"), jwk_cert:digest("sha1")); | 100 end |
115 return false; | |
116 end | 101 end |
117 end | 102 end |
103 | |
104 log("debug", "POSH authentication failed!"); | |
118 end); | 105 end); |