comparison mod_cloud_notify_encrypted/mod_cloud_notify_encrypted.lua @ 4327:beb3342f1137

mod_cloud_notify_encrypted: New module for Encrypted Push Notifications
author Matthew Wild <mwild1@gmail.com>
date Tue, 12 Jan 2021 15:43:26 +0000
parents
children 2a5164162708
comparison
equal deleted inserted replaced
4326:f6fdefc5c6ac 4327:beb3342f1137
1 local base64 = require "util.encodings".base64;
2 local ciphers = require "openssl.cipher";
3 local jid = require "util.jid";
4 local json = require "util.json";
5 local random = require "util.random";
6 local st = require "util.stanza";
7
8 local xmlns_jmi = "urn:xmpp:jingle-message:0";
9 local xmlns_push = "urn:xmpp:push:0";
10 local xmlns_push_encrypt = "tigase:push:encrypt:0";
11 local xmlns_push_encrypt_aes_128_gcm = "tigase:push:encrypt:aes-128-gcm";
12
13 -- https://xeps.tigase.net//docs/push-notifications/encrypt/#41-discovering-support
14 local function account_disco_info(event)
15 event.reply:tag("feature", {var=xmlns_push_encrypt}):up();
16 event.reply:tag("feature", {var=xmlns_push_encrypt_aes_128_gcm}):up();
17 end
18 module:hook("account-disco-info", account_disco_info);
19
20 function handle_register(event)
21 local encrypt = event.stanza:get_child("encrypt", xmlns_push_encrypt);
22 if not encrypt then return; end
23
24 local algorithm = encrypt.attr.alg;
25 if algorithm ~= "aes-128-gcm" then
26 event.origin.send(st.error_reply(
27 event.stanza, "modify", "feature-not-implemented", "Unknown encryption algorithm"
28 ));
29 return false;
30 end
31
32 local key_base64 = encrypt:get_text();
33 local key_binary = base64.decode(key_base64);
34 if not key_binary or #key_binary ~= 16 then
35 event.origin.send(st.error_reply(
36 event.stanza, "modify", "bad-request", "Invalid encryption key"
37 ));
38 return false;
39 end
40
41 event.push_info.encryption = {
42 algorithm = algorithm;
43 key_base64 = key_base64;
44 };
45 end
46
47 function handle_push(event)
48 local encryption = event.push_info.encryption;
49 if not encryption then return; end
50
51 if encryption.algorithm ~= "aes-128-gcm" then
52 event.reason = "Unsupported encryption algorithm: "..tostring(encryption.algorithm);
53 return true;
54 end
55
56 local push_summary = event.push_summary;
57
58 local original_stanza = event.original_stanza;
59
60 local push_payload = {
61 unread = push_summary["message-count"];
62 sender = push_summary["last-message-sender"];
63 };
64
65 if original_stanza.name == "message" then
66 if original_stanza.attr.type == "groupchat" then
67 push_payload.type = "groupchat";
68 push_payload.nickname = jid.resource(original_stanza.attr.from);
69 elseif original_stanza.attr.type ~= "error" then
70 local jmi_propose = original_stanza:get_child("propose", xmlns_jmi);
71 if jmi_propose then
72 push_payload.type = "call";
73 push_payload.sid = jmi_propose.attr.id;
74 else
75 push_payload.type = "chat";
76 end
77 end
78 elseif original_stanza.name == "presence"
79 and original_stanza.attr.type == "subscribe" then
80 push_payload.type = "subscribe";
81 end
82
83 local iv = random.bytes(12);
84 local key_binary = base64.decode(encryption.key_base64);
85 local push_json = json.encode(push_payload);
86
87 local encrypted_payload = ciphers.new("AES-128-GCM"):encrypt(key_binary, iv):final(push_json);
88 local encrypted_element = st.stanza("encrypted", { xmlns = xmlns_push_encrypt, iv = base64.encode(iv) })
89 :text(encrypted_payload);
90 -- Replace the unencrypted notification with the encrypted one
91 event.notification_stanza
92 :get_child("pubsub", "http://jabber.org/protocol/pubsub")
93 :get_child("publish")
94 :get_child("item")
95 :remove_children("notification", xmlns_push)
96 :add_child(encrypted_element);
97 end
98
99 module:hook("cloud_notify/registration", handle_register);
100 module:hook("cloud_notify/push", handle_push);