aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
authorMatthew Wild <mwild1@gmail.com>2022-06-24 17:03:28 +0100
committerMatthew Wild <mwild1@gmail.com>2022-06-24 17:03:28 +0100
commit4122acf8c8e80d7654e9ba7e1297a38e834f49de (patch)
tree1c131f8b8fc4885d3862b863606f968a13d4c158 /util
parentb357cf1be1572ae4298f5ef3b43134dd00bc5895 (diff)
downloadprosody-4122acf8c8e80d7654e9ba7e1297a38e834f49de.tar.gz
prosody-4122acf8c8e80d7654e9ba7e1297a38e834f49de.zip
util.paseto: Implementation of PASETO v4.public tokens
PASETO provides an alternative to JWT with the promise of fewer implementation pitfalls. The v4.public algorithm allows asymmetric cryptographically-verified token issuance and validation. In summary, such tokens can be issued by one party and securely verified by any other party independently using the public key of the issuer. This has a number of potential applications in a decentralized network and ecosystem such as XMPP. For example, such tokens could be combined with XEP-0317 to allow hats to be verified even in the context of a third-party MUC service.
Diffstat (limited to 'util')
-rw-r--r--util/paseto.lua123
1 files changed, 123 insertions, 0 deletions
diff --git a/util/paseto.lua b/util/paseto.lua
new file mode 100644
index 00000000..5f162ad0
--- /dev/null
+++ b/util/paseto.lua
@@ -0,0 +1,123 @@
+local crypto = require "util.crypto";
+local json = require "util.json";
+local base64_encode = require "util.encodings".base64.encode;
+local base64_decode = require "util.encodings".base64.decode;
+local secure_equals = require "util.hashes".equals;
+local bit = require "util.bitcompat";
+local s_pack = require "util.struct".pack;
+
+local s_gsub = string.gsub;
+
+local pubkey_methods = {};
+local privkey_methods = {};
+
+local v4_public_pubkey_mt = { __index = pubkey_methods };
+local v4_public_privkey_mt = { __index = privkey_methods };
+local v4_public = {};
+
+local b64url_rep = { ["+"] = "-", ["/"] = "_", ["="] = "", ["-"] = "+", ["_"] = "/" };
+local function b64url(data)
+ return (s_gsub(base64_encode(data), "[+/=]", b64url_rep));
+end
+local function unb64url(data)
+ return base64_decode(s_gsub(data, "[-_]", b64url_rep).."==");
+end
+
+local function le64(n)
+ return s_pack("<I8", bit.band(n, 0x7F));
+end
+
+local function pae(parts)
+ local o = { le64(#parts) };
+ for _, part in ipairs(parts) do
+ table.insert(o, le64(#part)..part);
+ end
+ return table.concat(o);
+end
+
+function privkey_methods:export()
+ return self.key:private_pem();
+end
+
+function pubkey_methods:export()
+ return self.key:public_pem();
+end
+
+function v4_public.sign(m, sk, f, i)
+ if getmetatable(sk) ~= v4_public_privkey_mt then
+ error("cannot sign v4.public tokens with this key");
+ end
+ if type(m) ~= "table" then
+ return nil, "PASETO payloads must be a table";
+ end
+ m = json.encode(m);
+ local h = "v4.public.";
+ local m2 = pae({ h, m, f or "", i or "" });
+ local sig = crypto.ed25519_sign(sk.key, m2);
+ if not f or f == "" then
+ return h..b64url(m..sig);
+ else
+ return h..b64url(m..sig).."."..b64url(f);
+ end
+end
+
+function v4_public.verify(tok, pk, expected_f, i)
+ if getmetatable(pk) ~= v4_public_pubkey_mt then
+ error("cannot verify v4.public tokens with this key");
+ end
+ local h, sm, f = tok:match("^(v4%.public%.)([^%.]+)%.?(.*)$");
+ if not h then
+ return nil, "invalid-token-format";
+ end
+ if expected_f then
+ if not f or not secure_equals(expected_f, f) then
+ return nil, "invalid-footer";
+ end
+ end
+ local raw_sm = unb64url(sm);
+ if not raw_sm or #raw_sm <= 64 then
+ return nil, "invalid-token-format";
+ end
+ local s, m = raw_sm:sub(-64), raw_sm:sub(1, -65);
+ local m2 = pae({ h, m, f or "", i or "" });
+ local ok = crypto.ed25519_verify(pk.key, m2, s);
+ if not ok then
+ return nil, "invalid-token";
+ end
+ local payload, err = json.decode(m);
+ if err ~= nil or type(payload) ~= "table" then
+ return nil, "json-decode-error";
+ end
+ return payload;
+end
+
+function v4_public.new_keypair()
+ local key = crypto.generate_ed25519_keypair();
+ return {
+ private_key = setmetatable({
+ key = key;
+ }, v4_public_privkey_mt);
+ public_key = setmetatable({
+ key = key;
+ }, v4_public_pubkey_mt);
+ };
+end
+
+function v4_public.import_public_key(pem)
+ local key = crypto.import_public_pem(pem);
+ return setmetatable({
+ key = key;
+ }, v4_public_pubkey_mt);
+end
+
+function v4_public.import_private_key(pem)
+ local key = crypto.import_private_pem(pem);
+ return setmetatable({
+ key = key;
+ }, v4_public_privkey_mt);
+end
+
+return {
+ pae = pae;
+ v4_public = v4_public;
+};