-- Copyright (C) 2013 Kim Alvefur
--
-- This file is MIT/X11 licensed.
local jid_compare = require "util.jid".compare;
local jid_split = require "util.jid".prepped_split;
local new_sasl = require "util.sasl".new;
local now = os.time;
local log = module._log;
local subject_alternative_name = "2.5.29.17";
local id_on_xmppAddr = "1.3.6.1.5.5.7.8.5";
local oid_emailAddress = "1.2.840.113549.1.9.1";
local cert_match = module:get_option("certificate_match", "xmppaddr");
local username_extractor = {};
function username_extractor.xmppaddr(cert, authz, session)
local extensions = cert:extensions();
local SANs = extensions[subject_alternative_name];
local xmppAddrs = SANs and SANs[id_on_xmppAddr];
if not xmppAddrs then
(session.log or log)("warn", "Client certificate contains no xmppAddrs");
return nil, false;
end
for i=1,#xmppAddrs do
if authz == "" or jid_compare(authz, xmppAddrs[i]) then
(session.log or log)("debug", "xmppAddrs[%d] %q matches authz %q", i, xmppAddrs[i], authz)
local username, host = jid_split(xmppAddrs[i]);
if host == module.host then
return username, true
end
end
end
end
function username_extractor.email(cert)
local subject = cert:subject();
for i=1,#subject do
local ava = subject[i];
if ava.oid == oid_emailAddress then
local username, host = jid_split(ava.value);
if host == module.host then
return username, true
end
end
end
end
local find_username = username_extractor[cert_match];
if not find_username then
module:log("error", "certificate_match = %q is not supported");
return
end
function get_sasl_handler(session)
return new_sasl(module.host, {
external = session.secure and function(authz)
if not session.secure then
-- getpeercertificate() on a TCP connection would be bad, abort!
(session.log or log)("error", "How did you manage to select EXTERNAL without TLS?");
return nil, false;
end
local sock = session.conn:socket();
local cert = sock:getpeercertificate();
if not cert then
(session.log or log)("warn", "No certificate provided");
return nil, false;
end
if not cert:validat(now()) then
(session.log or log)("warn", "Client certificate expired")
return nil, "expired";
end
local chain_valid, chain_errors = sock:getpeerverification();
if not chain_valid then
(session.log or log)("warn", "Invalid client certificate chain");
for i, error in ipairs(chain_errors) do
(session.log or log)("warn", "%d: %s", i, table.concat(error, ", "));
end
return nil, false;
end
return find_username(cert, authz, session);
end
});
end
module:provides "auth";