Mercurial > prosody-hg
comparison util/paseto.lua @ 12840:33d902b093f0
util.paseto: Add support for v3.local tokens
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Fri, 13 Jan 2023 14:38:05 +0000 |
| parents | 7db1c1da7bfd |
| children | d10957394a3c |
comparison
equal
deleted
inserted
replaced
| 12839:7db1c1da7bfd | 12840:33d902b093f0 |
|---|---|
| 1 local crypto = require "util.crypto"; | 1 local crypto = require "util.crypto"; |
| 2 local json = require "util.json"; | 2 local json = require "util.json"; |
| 3 local hashes = require "util.hashes"; | |
| 3 local base64_encode = require "util.encodings".base64.encode; | 4 local base64_encode = require "util.encodings".base64.encode; |
| 4 local base64_decode = require "util.encodings".base64.decode; | 5 local base64_decode = require "util.encodings".base64.decode; |
| 5 local secure_equals = require "util.hashes".equals; | 6 local secure_equals = require "util.hashes".equals; |
| 6 local bit = require "util.bitcompat"; | 7 local bit = require "util.bitcompat"; |
| 8 local hex = require "util.hex"; | |
| 9 local rand = require "util.random"; | |
| 7 local s_pack = require "util.struct".pack; | 10 local s_pack = require "util.struct".pack; |
| 8 | 11 |
| 9 local s_gsub = string.gsub; | 12 local s_gsub = string.gsub; |
| 10 | 13 |
| 11 local v4_public = {}; | 14 local v4_public = {}; |
| 112 | 115 |
| 113 function v4_public.new_verifier(public_key_pem, options) | 116 function v4_public.new_verifier(public_key_pem, options) |
| 114 return (select(2, v4_public.init(nil, public_key_pem, options))); | 117 return (select(2, v4_public.init(nil, public_key_pem, options))); |
| 115 end | 118 end |
| 116 | 119 |
| 120 local v3_local = { _key_mt = {} }; | |
| 121 | |
| 122 local function v3_local_derive_keys(k, n) | |
| 123 local tmp = hashes.hkdf_hmac_sha384(48, k, nil, "paseto-encryption-key"..n); | |
| 124 local Ek = tmp:sub(1, 32); | |
| 125 local n2 = tmp:sub(33); | |
| 126 local Ak = hashes.hkdf_hmac_sha384(48, k, nil, "paseto-auth-key-for-aead"..n); | |
| 127 return Ek, Ak, n2; | |
| 128 end | |
| 129 | |
| 130 function v3_local.encrypt(m, k, f, i) | |
| 131 assert(#k == 32) | |
| 132 if type(m) ~= "table" then | |
| 133 return nil, "PASETO payloads must be a table"; | |
| 134 end | |
| 135 m = json.encode(m); | |
| 136 local h = "v3.local."; | |
| 137 local n = rand.bytes(32); | |
| 138 local Ek, Ak, n2 = v3_local_derive_keys(k, n); | |
| 139 | |
| 140 local c = crypto.aes_256_ctr_encrypt(Ek, n2, m); | |
| 141 local m2 = pae({ h, n, c, f or "", i or "" }); | |
| 142 local t = hashes.hmac_sha384(Ak, m2); | |
| 143 | |
| 144 if not f or f == "" then | |
| 145 return h..b64url(n..c..t); | |
| 146 else | |
| 147 return h..b64url(n..c..t).."."..b64url(f); | |
| 148 end | |
| 149 end | |
| 150 | |
| 151 function v3_local.decrypt(tok, k, expected_f, i) | |
| 152 assert(#k == 32) | |
| 153 | |
| 154 local h, sm, f = tok:match("^(v3%.local%.)([^%.]+)%.?(.*)$"); | |
| 155 if not h then | |
| 156 return nil, "invalid-token-format"; | |
| 157 end | |
| 158 f = f and unb64url(f) or nil; | |
| 159 if expected_f then | |
| 160 if not f or not secure_equals(expected_f, f) then | |
| 161 return nil, "invalid-footer"; | |
| 162 end | |
| 163 end | |
| 164 local m = unb64url(sm); | |
| 165 if not m or #m <= 80 then | |
| 166 return nil, "invalid-token-format"; | |
| 167 end | |
| 168 local n, c, t = m:sub(1, 32), m:sub(33, -49), m:sub(-48); | |
| 169 local Ek, Ak, n2 = v3_local_derive_keys(k, n); | |
| 170 local preAuth = pae({ h, n, c, f or "", i or "" }); | |
| 171 local t2 = hashes.hmac_sha384(Ak, preAuth); | |
| 172 if not secure_equals(t, t2) then | |
| 173 return nil, "invalid-token"; | |
| 174 end | |
| 175 local m2 = crypto.aes_256_ctr_decrypt(Ek, n2, c); | |
| 176 if not m2 then | |
| 177 return nil, "invalid-token"; | |
| 178 end | |
| 179 | |
| 180 local payload, err = json.decode(m2); | |
| 181 if err ~= nil or type(payload) ~= "table" then | |
| 182 return nil, "json-decode-error"; | |
| 183 end | |
| 184 return payload; | |
| 185 end | |
| 186 | |
| 187 function v3_local.new_key() | |
| 188 return "secret-token:paseto.v3.local:"..hex.encode(rand.bytes(32)); | |
| 189 end | |
| 190 | |
| 191 function v3_local.init(key, options) | |
| 192 local encoded_key = key:match("^secret%-token:paseto%.v3%.local:(%x+)$"); | |
| 193 if not encoded_key or #encoded_key ~= 64 then | |
| 194 return error("invalid key for v3.local"); | |
| 195 end | |
| 196 local raw_key = hex.decode(encoded_key); | |
| 197 local default_footer = options and options.default_footer; | |
| 198 local default_assertion = options and options.default_implicit_assertion; | |
| 199 return function (token, token_footer, token_assertion) | |
| 200 return v3_local.encrypt(token, raw_key, token_footer or default_footer, token_assertion or default_assertion); | |
| 201 end, function (token, token_footer, token_assertion) | |
| 202 return v3_local.decrypt(token, raw_key, token_footer or default_footer, token_assertion or default_assertion); | |
| 203 end; | |
| 204 end | |
| 205 | |
| 206 function v3_local.new_signer(key, options) | |
| 207 return (v3_local.init(key, options)); | |
| 208 end | |
| 209 | |
| 210 function v3_local.new_verifier(key, options) | |
| 211 return (select(2, v3_local.init(key, options))); | |
| 212 end | |
| 213 | |
| 117 return { | 214 return { |
| 118 pae = pae; | 215 pae = pae; |
| 216 v3_local = v3_local; | |
| 119 v4_public = v4_public; | 217 v4_public = v4_public; |
| 120 }; | 218 }; |
