Software /
code /
prosody
File
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 |
line wrap: on
line source
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; };