mirror of
https://github.com/bjc/prosody.git
synced 2025-04-03 05:07:42 +03:00
util.paseto: Add support for v3.local tokens
This commit is contained in:
parent
cd9ef3168a
commit
99748c5655
2 changed files with 289 additions and 18 deletions
|
@ -4,28 +4,201 @@
|
|||
describe("util.paseto", function ()
|
||||
local paseto = require "util.paseto";
|
||||
local json = require "util.json";
|
||||
local hex = require "util.hex";
|
||||
|
||||
local function parse_test_cases(json_test_cases)
|
||||
local input_cases = json.decode(json_test_cases);
|
||||
local output_cases = {};
|
||||
for _, case in ipairs(input_cases) do
|
||||
assert.is_string(case.name, "Bad test case: expected name");
|
||||
assert.is_nil(output_cases[case.name], "Bad test case: duplicate name");
|
||||
output_cases[case.name] = function ()
|
||||
local verify_key = paseto.v4_public.import_public_key(case["public-key-pem"]);
|
||||
local payload, err = paseto.v4_public.verify(case.token, verify_key, case.footer, case["implicit-assertion"]);
|
||||
if case["expect-fail"] then
|
||||
assert.is_nil(payload);
|
||||
else
|
||||
assert.is_nil(err);
|
||||
assert.same(json.decode(case.payload), payload);
|
||||
end
|
||||
end;
|
||||
describe("v3.local", function ()
|
||||
local function parse_test_cases(json_test_cases)
|
||||
local input_cases = json.decode(json_test_cases);
|
||||
local output_cases = {};
|
||||
for _, case in ipairs(input_cases) do
|
||||
assert.is_string(case.name, "Bad test case: expected name");
|
||||
assert.is_nil(output_cases[case.name], "Bad test case: duplicate name");
|
||||
output_cases[case.name] = function ()
|
||||
local key = hex.decode(case.key);
|
||||
local payload, err = paseto.v3_local.decrypt(case.token, key, case.footer, case["implicit-assertion"]);
|
||||
if case["expect-fail"] then
|
||||
assert.is_nil(payload);
|
||||
else
|
||||
assert.is_nil(err);
|
||||
assert.same(json.decode(case.payload), payload);
|
||||
end
|
||||
end;
|
||||
end
|
||||
return output_cases;
|
||||
end
|
||||
return output_cases;
|
||||
end
|
||||
|
||||
local test_cases = parse_test_cases [=[[
|
||||
{
|
||||
"name": "3-E-1",
|
||||
"expect-fail": false,
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"nonce": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"token": "v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAsRm2EsD6yBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9Iza7teRdkiR89ZFyvPPsVjjFiepFUVcMa-LP18zV77f_crJrVXWa5PDNRkCSeHfBBeg",
|
||||
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "",
|
||||
"implicit-assertion": ""
|
||||
},
|
||||
{
|
||||
"name": "3-E-2",
|
||||
"expect-fail": false,
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"nonce": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"token": "v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAqhWxBMDgyBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9IzZfaZpReVpHlDSwfuygx1riVXYVs-UjcrG_apl9oz3jCVmmJbRuKn5ZfD8mHz2db0A",
|
||||
"payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "",
|
||||
"implicit-assertion": ""
|
||||
},
|
||||
{
|
||||
"name": "3-E-3",
|
||||
"expect-fail": false,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlxnt5xyhQjFJomwnt7WW_7r2VT0G704ifult011-TgLCyQ2X8imQhniG_hAQ4BydM",
|
||||
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "",
|
||||
"implicit-assertion": ""
|
||||
},
|
||||
{
|
||||
"name": "3-E-4",
|
||||
"expect-fail": false,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlBZa_gOpVj4gv0M9lV6Pwjp8JS_MmaZaTA1LLTULXybOBZ2S4xMbYqYmDRhh3IgEk",
|
||||
"payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "",
|
||||
"implicit-assertion": ""
|
||||
},
|
||||
{
|
||||
"name": "3-E-5",
|
||||
"expect-fail": false,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlkYSIbXOgVuIQL65UMdW9WcjOpmqvjqD40NNzed-XPqn1T3w-bJvitYpUJL_rmihc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9",
|
||||
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "{\"kid\":\"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo\"}",
|
||||
"implicit-assertion": ""
|
||||
},
|
||||
{
|
||||
"name": "3-E-6",
|
||||
"expect-fail": false,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmSeEMphEWHiwtDKJftg41O1F8Hat-8kQ82ZIAMFqkx9q5VkWlxZke9ZzMBbb3Znfo.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9",
|
||||
"payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "{\"kid\":\"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo\"}",
|
||||
"implicit-assertion": ""
|
||||
},
|
||||
{
|
||||
"name": "3-E-7",
|
||||
"expect-fail": false,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJkzWACWAIoVa0bz7EWSBoTEnS8MvGBYHHo6t6mJunPrFR9JKXFCc0obwz5N-pxFLOc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9",
|
||||
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "{\"kid\":\"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo\"}",
|
||||
"implicit-assertion": "{\"test-vector\":\"3-E-7\"}"
|
||||
},
|
||||
{
|
||||
"name": "3-E-8",
|
||||
"expect-fail": false,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmZHSSKYR6AnPYJV6gpHtx6dLakIG_AOPhu8vKexNyrv5_1qoom6_NaPGecoiz6fR8.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9",
|
||||
"payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "{\"kid\":\"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo\"}",
|
||||
"implicit-assertion": "{\"test-vector\":\"3-E-8\"}"
|
||||
},
|
||||
{
|
||||
"name": "3-E-9",
|
||||
"expect-fail": false,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlk1nli0_wijTH_vCuRwckEDc82QWK8-lG2fT9wQF271sgbVRVPjm0LwMQZkvvamqU.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24",
|
||||
"payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}",
|
||||
"footer": "arbitrary-string-that-isn't-json",
|
||||
"implicit-assertion": "{\"test-vector\":\"3-E-9\"}"
|
||||
},
|
||||
{
|
||||
"name": "3-F-3",
|
||||
"expect-fail": true,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v4.local.1JgN1UG8TFAYS49qsx8rxlwh-9E4ONUm3slJXYi5EibmzxpF0Q-du6gakjuyKCBX8TvnSLOKqCPu8Yh3WSa5yJWigPy33z9XZTJF2HQ9wlLDPtVn_Mu1pPxkTU50ZaBKblJBufRA.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24",
|
||||
"payload": null,
|
||||
"footer": "arbitrary-string-that-isn't-json",
|
||||
"implicit-assertion": "{\"test-vector\":\"3-F-3\"}"
|
||||
},
|
||||
{
|
||||
"name": "3-F-4",
|
||||
"expect-fail": true,
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"nonce": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"token": "v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAsRm2EsD6yBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9Iza7teRdkiR89ZFyvPPsVjjFiepFUVcMa-LP18zV77f_crJrVXWa5PDNRkCSeHfBBeh",
|
||||
"payload": null,
|
||||
"footer": "",
|
||||
"implicit-assertion": ""
|
||||
},
|
||||
{
|
||||
"name": "3-F-5",
|
||||
"expect-fail": true,
|
||||
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
|
||||
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
|
||||
"token": "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlkYSIbXOgVuIQL65UMdW9WcjOpmqvjqD40NNzed-XPqn1T3w-bJvitYpUJL_rmihc=.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9",
|
||||
"payload": null,
|
||||
"footer": "{\"kid\":\"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo\"}",
|
||||
"implicit-assertion": ""
|
||||
}
|
||||
]]=];
|
||||
for name, test in pairs(test_cases) do
|
||||
it("test case "..name, test);
|
||||
end
|
||||
|
||||
describe("basic sign/verify", function ()
|
||||
local key = paseto.v3_local.new_key();
|
||||
local sign, verify = paseto.v3_local.init(key);
|
||||
|
||||
local key2 = paseto.v3_local.new_key();
|
||||
local sign2, verify2 = paseto.v3_local.init(key2);
|
||||
|
||||
it("works", function ()
|
||||
local payload = { foo = "hello world", b = { 1, 2, 3 } };
|
||||
|
||||
local tok = sign(payload);
|
||||
assert.same(payload, verify(tok));
|
||||
assert.is_nil(verify2(tok));
|
||||
end);
|
||||
|
||||
it("rejects tokens if implicit assertion fails", function ()
|
||||
local payload = { foo = "hello world", b = { 1, 2, 3 } };
|
||||
local tok = sign(payload, nil, "my-custom-assertion");
|
||||
assert.is_nil(verify(tok, nil, "my-incorrect-assertion"));
|
||||
assert.is_nil(verify(tok, nil, nil));
|
||||
assert.same(payload, verify(tok, nil, "my-custom-assertion"));
|
||||
end);
|
||||
end);
|
||||
end);
|
||||
|
||||
describe("v4.public", function ()
|
||||
local function parse_test_cases(json_test_cases)
|
||||
local input_cases = json.decode(json_test_cases);
|
||||
local output_cases = {};
|
||||
for _, case in ipairs(input_cases) do
|
||||
assert.is_string(case.name, "Bad test case: expected name");
|
||||
assert.is_nil(output_cases[case.name], "Bad test case: duplicate name");
|
||||
output_cases[case.name] = function ()
|
||||
local verify_key = paseto.v4_public.import_public_key(case["public-key-pem"]);
|
||||
local payload, err = paseto.v4_public.verify(case.token, verify_key, case.footer, case["implicit-assertion"]);
|
||||
if case["expect-fail"] then
|
||||
assert.is_nil(payload);
|
||||
else
|
||||
assert.is_nil(err);
|
||||
assert.same(json.decode(case.payload), payload);
|
||||
end
|
||||
end;
|
||||
end
|
||||
return output_cases;
|
||||
end
|
||||
|
||||
local test_cases = parse_test_cases [=[[
|
||||
{
|
||||
"name": "4-S-1",
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
local crypto = require "util.crypto";
|
||||
local json = require "util.json";
|
||||
local hashes = require "util.hashes";
|
||||
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 hex = require "util.hex";
|
||||
local rand = require "util.random";
|
||||
local s_pack = require "util.struct".pack;
|
||||
|
||||
local s_gsub = string.gsub;
|
||||
|
@ -114,7 +117,102 @@ function v4_public.new_verifier(public_key_pem, options)
|
|||
return (select(2, v4_public.init(nil, public_key_pem, options)));
|
||||
end
|
||||
|
||||
local v3_local = { _key_mt = {} };
|
||||
|
||||
local function v3_local_derive_keys(k, n)
|
||||
local tmp = hashes.hkdf_hmac_sha384(48, k, nil, "paseto-encryption-key"..n);
|
||||
local Ek = tmp:sub(1, 32);
|
||||
local n2 = tmp:sub(33);
|
||||
local Ak = hashes.hkdf_hmac_sha384(48, k, nil, "paseto-auth-key-for-aead"..n);
|
||||
return Ek, Ak, n2;
|
||||
end
|
||||
|
||||
function v3_local.encrypt(m, k, f, i)
|
||||
assert(#k == 32)
|
||||
if type(m) ~= "table" then
|
||||
return nil, "PASETO payloads must be a table";
|
||||
end
|
||||
m = json.encode(m);
|
||||
local h = "v3.local.";
|
||||
local n = rand.bytes(32);
|
||||
local Ek, Ak, n2 = v3_local_derive_keys(k, n);
|
||||
|
||||
local c = crypto.aes_256_ctr_encrypt(Ek, n2, m);
|
||||
local m2 = pae({ h, n, c, f or "", i or "" });
|
||||
local t = hashes.hmac_sha384(Ak, m2);
|
||||
|
||||
if not f or f == "" then
|
||||
return h..b64url(n..c..t);
|
||||
else
|
||||
return h..b64url(n..c..t).."."..b64url(f);
|
||||
end
|
||||
end
|
||||
|
||||
function v3_local.decrypt(tok, k, expected_f, i)
|
||||
assert(#k == 32)
|
||||
|
||||
local h, sm, f = tok:match("^(v3%.local%.)([^%.]+)%.?(.*)$");
|
||||
if not h then
|
||||
return nil, "invalid-token-format";
|
||||
end
|
||||
f = f and unb64url(f) or nil;
|
||||
if expected_f then
|
||||
if not f or not secure_equals(expected_f, f) then
|
||||
return nil, "invalid-footer";
|
||||
end
|
||||
end
|
||||
local m = unb64url(sm);
|
||||
if not m or #m <= 80 then
|
||||
return nil, "invalid-token-format";
|
||||
end
|
||||
local n, c, t = m:sub(1, 32), m:sub(33, -49), m:sub(-48);
|
||||
local Ek, Ak, n2 = v3_local_derive_keys(k, n);
|
||||
local preAuth = pae({ h, n, c, f or "", i or "" });
|
||||
local t2 = hashes.hmac_sha384(Ak, preAuth);
|
||||
if not secure_equals(t, t2) then
|
||||
return nil, "invalid-token";
|
||||
end
|
||||
local m2 = crypto.aes_256_ctr_decrypt(Ek, n2, c);
|
||||
if not m2 then
|
||||
return nil, "invalid-token";
|
||||
end
|
||||
|
||||
local payload, err = json.decode(m2);
|
||||
if err ~= nil or type(payload) ~= "table" then
|
||||
return nil, "json-decode-error";
|
||||
end
|
||||
return payload;
|
||||
end
|
||||
|
||||
function v3_local.new_key()
|
||||
return "secret-token:paseto.v3.local:"..hex.encode(rand.bytes(32));
|
||||
end
|
||||
|
||||
function v3_local.init(key, options)
|
||||
local encoded_key = key:match("^secret%-token:paseto%.v3%.local:(%x+)$");
|
||||
if not encoded_key or #encoded_key ~= 64 then
|
||||
return error("invalid key for v3.local");
|
||||
end
|
||||
local raw_key = hex.decode(encoded_key);
|
||||
local default_footer = options and options.default_footer;
|
||||
local default_assertion = options and options.default_implicit_assertion;
|
||||
return function (token, token_footer, token_assertion)
|
||||
return v3_local.encrypt(token, raw_key, token_footer or default_footer, token_assertion or default_assertion);
|
||||
end, function (token, token_footer, token_assertion)
|
||||
return v3_local.decrypt(token, raw_key, token_footer or default_footer, token_assertion or default_assertion);
|
||||
end;
|
||||
end
|
||||
|
||||
function v3_local.new_signer(key, options)
|
||||
return (v3_local.init(key, options));
|
||||
end
|
||||
|
||||
function v3_local.new_verifier(key, options)
|
||||
return (select(2, v3_local.init(key, options)));
|
||||
end
|
||||
|
||||
return {
|
||||
pae = pae;
|
||||
v3_local = v3_local;
|
||||
v4_public = v4_public;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue