From 0bcbbed75359c7460463e6f33b81a74dcb1b801f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Feb 2020 01:24:25 +0100 Subject: util.jwt: Basic JSON Web Token library supporting HS256 tokens --- spec/util_jwt_spec.lua | 20 ++++++++++++++++++++ util/jwt.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 spec/util_jwt_spec.lua create mode 100644 util/jwt.lua diff --git a/spec/util_jwt_spec.lua b/spec/util_jwt_spec.lua new file mode 100644 index 00000000..5e688f7b --- /dev/null +++ b/spec/util_jwt_spec.lua @@ -0,0 +1,20 @@ +local jwt = require "util.jwt"; + +describe("util.jwt", function () + it("validates", function () + local key = "secret"; + local token = jwt.sign(key, { payload = "this" }); + assert.string(token); + local ok, parsed = jwt.verify(key, token); + assert.truthy(ok) + assert.same({ payload = "this" }, parsed); + end); + it("rejects invalid", function () + local key = "secret"; + local token = jwt.sign("wrong", { payload = "this" }); + assert.string(token); + local ok, err = jwt.verify(key, token); + assert.falsy(ok) + end); +end); + diff --git a/util/jwt.lua b/util/jwt.lua new file mode 100644 index 00000000..2b172d38 --- /dev/null +++ b/util/jwt.lua @@ -0,0 +1,50 @@ +local s_gsub = string.gsub; +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 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 static_header = b64url('{"alg":"HS256","typ":"JWT"}') .. '.'; + +local function sign(key, payload) + local encoded_payload = json.encode(payload); + local signed = static_header .. b64url(encoded_payload); + local signature = hashes.hmac_sha256(key, signed); + return signed .. "." .. b64url(signature); +end + +local jwt_pattern = "^(([A-Za-z0-9-_]+)%.([A-Za-z0-9-_]+))%.([A-Za-z0-9-_]+)$" +local function verify(key, blob) + local signed, bheader, bpayload, signature = string.match(blob, jwt_pattern); + if not signed then + return nil, "invalid-encoding"; + end + local header = json.decode(unb64url(bheader)); + if not header or type(header) ~= "table" then + return nil, "invalid-header"; + elseif header.alg ~= "HS256" then + return nil, "unsupported-algorithm"; + end + if b64url(hashes.hmac_sha256(key, signed)) ~= signature then + return false, "signature-mismatch"; + end + local payload, err = json.decode(unb64url(bpayload)); + if err ~= nil then + return nil, "json-decode-error"; + end + return true, payload; +end + +return { + sign = sign; + verify = verify; +}; + -- cgit v1.2.3