Software /
code /
prosody
Changeset
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 |
parents | 12693:7c5afbdcbc77 |
children | 12695:6aaa604fdfd5 |
files | util/paseto.lua |
diffstat | 1 files changed, 123 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util/paseto.lua Fri Jun 24 17:03:28 2022 +0100 @@ -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; +};