Software /
code /
prosody
File
util/paseto.lua @ 12705:008a7097fdc5
util.jwt: Provide built-in token expiry support (defaults to 3600s lifetime)
To avoid every user of the library needing to add and verify expiry info, this
is now handled by util.jwt itself (if not overridden or disabled).
Issuing tokens that are valid forever is bad practice and rarely desired, and
the default token lifetime is now 3600s (1 hour).
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Mon, 11 Jul 2022 13:28:29 +0100 |
parent | 12694:26a004c96ef8 |
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; };