Comparison

plugins/mod_s2s_auth_dane_in.lua @ 13297:7264c4d16072

mod_s2s_auth_dane_in: DANE support for s2sin Complements the DANE support for outgoing connections included in net.connect
author Kim Alvefur <zash@zash.se>
date Wed, 01 Nov 2023 22:49:56 +0100
child 13322:28211ed70b4c
comparison
equal deleted inserted replaced
13296:803a4ef6244d 13297:7264c4d16072
1 module:set_global();
2
3 local dns = require "prosody.net.adns";
4 local async = require "prosody.util.async";
5 local encodings = require "prosody.util.encodings";
6 local hashes = require "prosody.util.hashes";
7 local promise = require "prosody.util.promise";
8 local x509 = require "prosody.util.x509";
9
10 local idna_to_ascii = encodings.idna.to_ascii;
11 local sha256 = hashes.sha256;
12 local sha512 = hashes.sha512;
13
14 local use_dane = module:get_option_boolean("use_dane", nil);
15 if use_dane == nil then
16 module:log("warn", "DANE support incomplete, add use_dane = true in the global section to support outgoing s2s connections");
17 elseif use_dane == false then
18 module:log("debug", "DANE support disabled with use_dane = false, disabling.")
19 return
20 end
21
22 local function ensure_secure(r)
23 assert(r.secure, "insecure");
24 return r;
25 end
26
27 local lazy_tlsa_mt = {
28 __index = function(t, i)
29 if i == 1 then
30 local h = sha256(t[0]);
31 t[1] = h;
32 return h;
33 elseif i == 2 then
34 local h = sha512(t[0]);
35 t[1] = h;
36 return h;
37 end
38 end;
39 }
40 local function lazy_hash(t)
41 return setmetatable(t, lazy_tlsa_mt);
42 end
43
44 module:hook("s2s-check-certificate", function(event)
45 local session, host, cert = event.session, event.host, event.cert;
46 local log = session.log or module._log;
47
48 if not host or not cert or session.direction ~= "incoming" then
49 return
50 end
51
52 local by_select_match = {
53 [0] = lazy_hash {
54 -- cert
55 [0] = x509.pem2der(cert:pem());
56
57 };
58 }
59 if cert.pubkey then
60 by_select_match[1] = lazy_hash {
61 -- spki
62 [0] = x509.pem2der(cert:pubkey());
63 };
64 end
65
66 local resolver = dns.resolver();
67
68 local dns_domain = idna_to_ascii(host);
69
70 local function fetch_tlsa(res)
71 local tlsas = {};
72 for _, rr in ipairs(res) do
73 table.insert(tlsas, resolver:lookup_promise(("_%d._tcp.%s"):format(rr.srv.port, rr.srv.target), "TLSA"):next(ensure_secure));
74 end
75 return promise.all(tlsas);
76 end
77
78 local ret = async.wait_for(promise.all({
79 resolver:lookup_promise("_xmpps-server._tcp." .. dns_domain, "SRV"):next(ensure_secure):next(fetch_tlsa);
80 resolver:lookup_promise("_xmpp-server._tcp." .. dns_domain, "SRV"):next(ensure_secure):next(fetch_tlsa);
81 }));
82
83 if not ret then
84 return
85 end
86
87 local found_supported = false;
88 for _, by_proto in ipairs(ret) do
89 for _, by_srv in ipairs(by_proto) do
90 for _, by_target in ipairs(by_srv) do
91 for _, rr in ipairs(by_target) do
92 if rr.tlsa.use == 3 and by_select_match[rr.tlsa.select] and rr.tlsa.match <= 2 then
93 found_supported = true;
94 if rr.tlsa.data == by_select_match[rr.tlsa.select][rr.tlsa.match] then
95 module:log("debug", "%s matches", rr)
96 session.cert_chain_status = "valid";
97 session.cert_identity_status = "valid";
98 return true;
99 end
100 else
101 log("debug", "Unsupported DANE TLSA record: %s", rr);
102 end
103 end
104 end
105 end
106 end
107
108 if found_supported then
109 session.cert_chain_status = "invalid";
110 session.cert_identity_status = nil;
111 return true;
112 end
113
114 end, 800);