Software /
code /
prosody-modules
Diff
mod_s2s_auth_posh/mod_s2s_auth_posh.lua @ 3198:f3e452b43cfe
mod_s2s_auth_posh: PKIX over Secure HTTP
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Wed, 21 May 2014 23:01:47 +0200 |
child | 3199:cb7c24305ed2 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_s2s_auth_posh/mod_s2s_auth_posh.lua Wed May 21 23:01:47 2014 +0200 @@ -0,0 +1,118 @@ +-- Copyright (C) 2013 - 2014 Tobias Markmann +-- This file is MIT/X11 licensed. +-- +-- Implements authentication via POSH (PKIX over Secure HTTP) +-- http://tools.ietf.org/html/draft-miller-posh-03 +-- +module:set_global(); +--local https = require 'ssl.https' +--local http = require "socket.http"; +local json = require 'util.json' +local serialization = require 'util.serialization' + +local nameprep = require "util.encodings".stringprep.nameprep; +local to_unicode = require "util.encodings".idna.to_unicode; +local cert_verify_identity = require "util.x509".verify_identity; +local der2pem = require"util.x509".der2pem; +local base64 = require"util.encodings".base64; + +local function posh_lookup(host_session, resume) + -- do nothing if posh info already exists + if host_session.posh ~= nil then return end + + (host_session.log or module._log)("debug", "DIRECTION: %s", tostring(host_session.direction)); + + local target_host = false; + if host_session.direction == "incoming" then + target_host = host_session.from_host; + elseif host_session.direction == "outgoing" then + target_host = host_session.to_host; + end + + local url = "https://"..target_host.."/.well-known/posh._xmpp-server._tcp.json" + + (host_session.log or module._log)("debug", "Request POSH information for %s", tostring(target_host)); + local request = http.request(url, nil, function(response, code, req) + (host_session.log or module._log)("debug", "Received POSH response"); + local jwk = json.decode(response); + if not jwk then + (host_session.log or module._log)("error", "POSH response is not valid JSON!"); + (host_session.log or module._log)("debug", tostring(response)); + end + host_session.posh = {}; + host_session.posh.jwk = jwk; + resume() + end) + return true; +end + +function module.add_host(module) + local function on_new_s2s(event) + local host_session = event.origin; + if host_session.type == "s2sout" or host_session.type == "s2sin" or host_session.posh ~= nil then return end -- Already authenticated + + host_session.log("debug", "Pausing connection until POSH lookup is completed"); + host_session.conn:pause() + local function resume() + host_session.log("debug", "POSH lookup completed, resuming connection"); + host_session.conn:resume() + end + if not posh_lookup(host_session, resume) then + resume(); + end + end + + -- New outgoing connections + module:hook("stanza/http://etherx.jabber.org/streams:features", on_new_s2s, 501); + module:hook("s2sout-authenticate-legacy", on_new_s2s, 200); + + -- New incoming connections + module:hook("s2s-stream-features", on_new_s2s, 10); + + module:hook("s2s-authenticated", function(event) + local session = event.session; + if session.posh and not session.secure then + -- Bogus replies should trigger this path + -- How does this interact with Dialback? + session:close({ + condition = "policy-violation", + text = "Secure server-to-server communication is required but was not " + ..((session.direction == "outgoing" and "offered") or "used") + }); + return false; + end + -- Cleanup + session.posh = nil; + end); +end + +-- Do POSH authentication +module:hook("s2s-check-certificate", function(event) + local session, cert = event.session, event.cert; + (session.log or module._log)("info", "Trying POSH authentication."); + -- if session.cert_identity_status ~= "valid" and session.posh then + if session.posh then + local target_host = event.host; + + local jwk = session.posh.jwk; + + local connection_certs = session.conn:socket():getpeerchain(); + + local x5c_table = jwk.keys[1].x5c; + + local wire_cert = connection_certs[1]; + local jwk_cert = ssl.x509.load(der2pem(base64.decode(x5c_table[1]))); + + if (wire_cert and jwk_cert and + wire_cert:digest("sha1") == jwk_cert:digest("sha1")) then + session.cert_chain_status = "valid"; + session.cert_identity_status = "valid"; + (session.log or module._log)("debug", "POSH authentication succeeded!"); + return true; + else + (session.log or module._log)("debug", "POSH authentication failed!"); + (session.log or module._log)("debug", "(top wire sha1 vs top jwk sha1) = (%s vs %s)", wire_cert:digest("sha1"), jwk_cert:digest("sha1")); + return false; + end + end +end);