Comparison

util/paseto.lua @ 12694:26a004c96ef8

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.
author Matthew Wild <mwild1@gmail.com>
date Fri, 24 Jun 2022 17:03:28 +0100
child 12709:b3f7c77c1f08
comparison
equal deleted inserted replaced
12693:7c5afbdcbc77 12694:26a004c96ef8
1 local crypto = require "util.crypto";
2 local json = require "util.json";
3 local base64_encode = require "util.encodings".base64.encode;
4 local base64_decode = require "util.encodings".base64.decode;
5 local secure_equals = require "util.hashes".equals;
6 local bit = require "util.bitcompat";
7 local s_pack = require "util.struct".pack;
8
9 local s_gsub = string.gsub;
10
11 local pubkey_methods = {};
12 local privkey_methods = {};
13
14 local v4_public_pubkey_mt = { __index = pubkey_methods };
15 local v4_public_privkey_mt = { __index = privkey_methods };
16 local v4_public = {};
17
18 local b64url_rep = { ["+"] = "-", ["/"] = "_", ["="] = "", ["-"] = "+", ["_"] = "/" };
19 local function b64url(data)
20 return (s_gsub(base64_encode(data), "[+/=]", b64url_rep));
21 end
22 local function unb64url(data)
23 return base64_decode(s_gsub(data, "[-_]", b64url_rep).."==");
24 end
25
26 local function le64(n)
27 return s_pack("<I8", bit.band(n, 0x7F));
28 end
29
30 local function pae(parts)
31 local o = { le64(#parts) };
32 for _, part in ipairs(parts) do
33 table.insert(o, le64(#part)..part);
34 end
35 return table.concat(o);
36 end
37
38 function privkey_methods:export()
39 return self.key:private_pem();
40 end
41
42 function pubkey_methods:export()
43 return self.key:public_pem();
44 end
45
46 function v4_public.sign(m, sk, f, i)
47 if getmetatable(sk) ~= v4_public_privkey_mt then
48 error("cannot sign v4.public tokens with this key");
49 end
50 if type(m) ~= "table" then
51 return nil, "PASETO payloads must be a table";
52 end
53 m = json.encode(m);
54 local h = "v4.public.";
55 local m2 = pae({ h, m, f or "", i or "" });
56 local sig = crypto.ed25519_sign(sk.key, m2);
57 if not f or f == "" then
58 return h..b64url(m..sig);
59 else
60 return h..b64url(m..sig).."."..b64url(f);
61 end
62 end
63
64 function v4_public.verify(tok, pk, expected_f, i)
65 if getmetatable(pk) ~= v4_public_pubkey_mt then
66 error("cannot verify v4.public tokens with this key");
67 end
68 local h, sm, f = tok:match("^(v4%.public%.)([^%.]+)%.?(.*)$");
69 if not h then
70 return nil, "invalid-token-format";
71 end
72 if expected_f then
73 if not f or not secure_equals(expected_f, f) then
74 return nil, "invalid-footer";
75 end
76 end
77 local raw_sm = unb64url(sm);
78 if not raw_sm or #raw_sm <= 64 then
79 return nil, "invalid-token-format";
80 end
81 local s, m = raw_sm:sub(-64), raw_sm:sub(1, -65);
82 local m2 = pae({ h, m, f or "", i or "" });
83 local ok = crypto.ed25519_verify(pk.key, m2, s);
84 if not ok then
85 return nil, "invalid-token";
86 end
87 local payload, err = json.decode(m);
88 if err ~= nil or type(payload) ~= "table" then
89 return nil, "json-decode-error";
90 end
91 return payload;
92 end
93
94 function v4_public.new_keypair()
95 local key = crypto.generate_ed25519_keypair();
96 return {
97 private_key = setmetatable({
98 key = key;
99 }, v4_public_privkey_mt);
100 public_key = setmetatable({
101 key = key;
102 }, v4_public_pubkey_mt);
103 };
104 end
105
106 function v4_public.import_public_key(pem)
107 local key = crypto.import_public_pem(pem);
108 return setmetatable({
109 key = key;
110 }, v4_public_pubkey_mt);
111 end
112
113 function v4_public.import_private_key(pem)
114 local key = crypto.import_private_pem(pem);
115 return setmetatable({
116 key = key;
117 }, v4_public_privkey_mt);
118 end
119
120 return {
121 pae = pae;
122 v4_public = v4_public;
123 };