Software /
code /
prosody
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 |
parent | 12839:7db1c1da7bfd |
child | 12975: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 }; |