From 91d40d91ff9fc6c335f8bd2c133bc604b7853d3c Mon Sep 17 00:00:00 2001 From: burrscurr Date: Wed, 27 Oct 2021 14:28:50 +0200 Subject: [PATCH] Check nbf and exp claims This commit adds support for the reserved nbf (not before) and exp (expires) claims. In addition to a valid signature, the current time to be within the range expressed in the nbf and exp claims. Both nbf and exp are optional: If omitted or assigned an invalid value, the lower or upper time boundary does not apply, respectively. --- Makefile | 2 +- pgjwt--0.1.1--0.2.0.sql | 29 ++++++++++++ pgjwt.control | 2 +- test.sql | 98 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 pgjwt--0.1.1--0.2.0.sql diff --git a/Makefile b/Makefile index a67f4fc..dbf346d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ EXTENSION = pgjwt -DATA = pgjwt--0.1.1.sql pgjwt--0.1.0--0.1.1.sql +DATA = pgjwt--0.1.1.sql pgjwt--0.1.0--0.1.1.sql pgjwt--0.1.1--0.2.0.sql # postgres build stuff PG_CONFIG = pg_config diff --git a/pgjwt--0.1.1--0.2.0.sql b/pgjwt--0.1.1--0.2.0.sql new file mode 100644 index 0000000..ab14fcd --- /dev/null +++ b/pgjwt--0.1.1--0.2.0.sql @@ -0,0 +1,29 @@ +CREATE OR REPLACE FUNCTION try_cast_double(inp text) +RETURNS double precision AS $$ + BEGIN + BEGIN + RETURN inp::double precision; + EXCEPTION + WHEN OTHERS THEN RETURN NULL; + END; + END; +$$ language plpgsql IMMUTABLE; + + +CREATE OR REPLACE FUNCTION verify(token text, secret text, algorithm text DEFAULT 'HS256') +RETURNS table(header json, payload json, valid boolean) LANGUAGE sql AS $$ + SELECT + jwt.header AS header, + jwt.payload AS payload, + jwt.signature_ok AND tstzrange( + to_timestamp(try_cast_double(jwt.payload->>'nbf')), + to_timestamp(try_cast_double(jwt.payload->>'exp')) + ) @> CURRENT_TIMESTAMP AS valid + FROM ( + SELECT + convert_from(@extschema@.url_decode(r[1]), 'utf8')::json AS header, + convert_from(@extschema@.url_decode(r[2]), 'utf8')::json AS payload, + r[3] = @extschema@.algorithm_sign(r[1] || '.' || r[2], secret, algorithm) AS signature_ok + FROM regexp_split_to_array(token, '\.') r + ) jwt +$$ IMMUTABLE; diff --git a/pgjwt.control b/pgjwt.control index 18f7a1c..44d7cd2 100644 --- a/pgjwt.control +++ b/pgjwt.control @@ -1,6 +1,6 @@ # pgjwt extension comment = 'JSON Web Token API for Postgresql' -default_version = '0.1.1' +default_version = '0.2.0' relocatable = false requires = pgcrypto superuser = false diff --git a/test.sql b/test.sql index b3e77d5..d7a0ca8 100644 --- a/test.sql +++ b/test.sql @@ -14,7 +14,7 @@ CREATE EXTENSION pgtap; CREATE EXTENSION pgjwt; BEGIN; -SELECT plan(14); +SELECT plan(23); SELECT is(sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret'), @@ -131,5 +131,101 @@ SELECT ); +SELECT + results_eq( + $$SELECT header::text, payload::text, valid FROM verify( + E'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiJuby1kb3VibGUifQ.6TDHvMKq3Z67KaexRMuhoQ20sYSj9jConcUCO3g2bGyHXACq-FPkJIRAsy1xX90fWKieIAW_tz-4bbFLwwOTPg', + 'secret', 'HS512')$$, + $$VALUES ('{"alg":"HS512","typ":"JWT"}', '{"nbf":"no-double"}', true)$$, + 'verify() should ignore a nbf claim with a invalid value' +); + + +SELECT + results_eq( + $$SELECT header::text, payload::text, valid FROM verify( + E'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOiI3NXoifQ.nfXYiSNNdYNsLrQp5Zry9p0xDCh_CkMSY1dOdqDCLc1YrDxrItwEmZIlTi3BBO8_9OCameSKMyGysYGDCNoaRg', + 'secret', 'HS512')$$, + $$VALUES ('{"alg":"HS512","typ":"JWT"}', '{"exp":"75z"}', true)$$, + 'verify() should ignore a exp claim with a invalid value' +); + + +SELECT + results_eq( + $$SELECT valid + FROM verify(sign(json_build_object('nbf', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) + 1), 'secret', 'HS512'), 'secret', 'HS512')$$, + $$VALUES (false)$$, + 'verify() should not verify a jwt checked before its nbf claim' +); + + +SELECT + results_eq( + $$SELECT valid + FROM verify(sign(json_build_object('nbf', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) - 1), 'secret', 'HS512'), 'secret', 'HS512')$$, + $$VALUES (true)$$, + 'verify() should verify a jwt checked after its nbf claim' +); + + +SELECT + results_eq( + $$SELECT valid + FROM verify(sign(json_build_object('exp', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) - 1), 'secret', 'HS512'), 'secret', 'HS512')$$, + $$VALUES (false)$$, + 'verify() should not verify a jwt checked after its exp claim' +); + + +SELECT + results_eq( + $$SELECT valid + FROM verify(sign(json_build_object('exp', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) + 1), 'secret', 'HS512'), 'secret', 'HS512')$$, + $$VALUES (true)$$, + 'verify() should verify a jwt checked before its exp claim' +); + + + +SELECT + results_eq( + $$SELECT valid + FROM verify(sign(json_build_object('exp', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) + 1), 'secret', 'HS512'), 'secret', 'HS512')$$, + $$VALUES (true)$$, + 'verify() should verify a jwt checked before its exp claim' +); + +SELECT + results_eq( + $$SELECT valid + FROM verify(sign( + json_build_object( + 'nbf', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) - 3, + 'exp', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) - 1 + ), + 'secret', 'HS512'), 'secret', 'HS512')$$, + $$VALUES (false)$$, + 'verify() should not verify a jwt checked outside of its claimed nbf-exp range' +); + + +SELECT + results_eq( + $$SELECT valid + FROM verify( + sign( + json_build_object( + 'nbf', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) - 3, + 'exp', EXTRACT (EPOCH FROM CURRENT_TIMESTAMP) + 3 + ), + 'secret', 'HS512' + ), + 'secret', 'HS512')$$, + $$VALUES (true)$$, + 'verify() should verify a jwt checked within its claimed nbf-exp range' +); + + SELECT * FROM finish(); ROLLBACK;