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
parents f3e452b43cfe
children 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);