From ae16ddcac75cb94ea1d699b19b0bd8ed37fd5030 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 1 Jul 2022 18:51:15 +0100 Subject: util.jwt: Add support/tests for ES256 via improved API and using util.crypto In many cases code will be either signing or verifying. With asymmetric algorithms it's clearer and more efficient to just state that once, instead of passing keys (and possibly other parameters) with every sign/verify call. This also allows earlier validation of the key used. The previous (HS256-only) sign/verify methods continue to be exposed for backwards-compatibility. --- util/jwt.lua | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 121 insertions(+), 19 deletions(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index bf106dfa..58888b5d 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -1,4 +1,5 @@ local s_gsub = string.gsub; +local crypto = require "util.crypto"; local json = require "util.json"; local hashes = require "util.hashes"; local base64_encode = require "util.encodings".base64.encode; @@ -13,17 +14,8 @@ 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 function decode_jwt(blob, expected_alg) local signed, bheader, bpayload, signature = string.match(blob, jwt_pattern); if not signed then return nil, "invalid-encoding"; @@ -31,21 +23,131 @@ local function verify(key, blob) local header = json.decode(unb64url(bheader)); if not header or type(header) ~= "table" then return nil, "invalid-header"; - elseif header.alg ~= "HS256" then + elseif header.alg ~= expected_alg then return nil, "unsupported-algorithm"; end - if not secure_equals(b64url(hashes.hmac_sha256(key, signed)), signature) then - return false, "signature-mismatch"; + return signed, signature, bpayload; +end + +local function new_static_header(algorithm_name) + return b64url('{"alg":"'..algorithm_name..'","typ":"JWT"}') .. '.'; +end + +-- HS*** family +local function new_hmac_algorithm(name, hmac) + local static_header = new_static_header(name); + + local function sign(key, payload) + local encoded_payload = json.encode(payload); + local signed = static_header .. b64url(encoded_payload); + local signature = hmac(key, signed); + return signed .. "." .. b64url(signature); + end + + local function verify(key, blob) + local signed, signature, raw_payload = decode_jwt(blob, name); + if not signed then return nil, signature; end -- nil, err + + if not secure_equals(b64url(hmac(key, signed)), signature) then + return false, "signature-mismatch"; + end + local payload, err = json.decode(unb64url(raw_payload)); + if err ~= nil then + return nil, "json-decode-error"; + end + return true, payload; end - local payload, err = json.decode(unb64url(bpayload)); - if err ~= nil then - return nil, "json-decode-error"; + + local function load_key(key) + assert(type(key) == "string", "key must be string (long, random, secure)"); + return key; + end + + return { sign = sign, verify = verify, load_key = load_key }; +end + +-- ES*** family +local function new_ecdsa_algorithm(name, c_sign, c_verify) + local static_header = new_static_header(name); + + return { + sign = function (private_key, payload) + local encoded_payload = json.encode(payload); + local signed = static_header .. b64url(encoded_payload); + + local der_sig = c_sign(private_key, signed); + + local r, s = crypto.parse_ecdsa_signature(der_sig); + + return signed.."."..b64url(r..s); + end; + + verify = function (public_key, blob) + local signed, signature, raw_payload = decode_jwt(blob, name); + if not signed then return nil, signature; end -- nil, err + + local raw_signature = unb64url(signature); + + local der_sig = crypto.build_ecdsa_signature(raw_signature:sub(1, 32), raw_signature:sub(33, 64)); + if not der_sig then + return false, "signature-mismatch"; + end + + local verify_ok = c_verify(public_key, signed, der_sig); + if not verify_ok then + return false, "signature-mismatch"; + end + + local payload, err = json.decode(unb64url(raw_payload)); + if err ~= nil then + return nil, "json-decode-error"; + end + + return true, payload; + end; + + load_public_key = function (public_key_pem) + local key = assert(crypto.import_public_pem(public_key_pem)); + assert(key:get_type() == "id-ecPublicKey", "incorrect key type"); + return key; + end; + + load_private_key = function (private_key_pem) + local key = assert(crypto.import_private_pem(private_key_pem)); + assert(key:get_type() == "id-ecPublicKey", "incorrect key type"); + return key; + end; + }; +end + +local algorithms = { + HS256 = new_hmac_algorithm("HS256", hashes.hmac_sha256); + ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify); +}; + +local function new_signer(algorithm, key_input) + local impl = assert(algorithms[algorithm], "Unknown JWT algorithm: "..algorithm); + local key = (impl.load_private_key or impl.load_key)(key_input); + local sign = impl.sign; + return function (payload) + return sign(key, payload); + end +end + +local function new_verifier(algorithm, key_input) + local impl = assert(algorithms[algorithm], "Unknown JWT algorithm: "..algorithm); + local key = (impl.load_public_key or impl.load_key)(key_input); + local verify = impl.verify; + return function (token) + return verify(key, token); end - return true, payload; end return { - sign = sign; - verify = verify; + new_signer = new_signer; + new_verifier = new_verifier; + -- Deprecated + sign = algorithms.HS256.sign; + verify = algorithms.HS256.verify; }; -- cgit v1.2.3 From 0b0555c3390287d2416485b1549186a282c6d1b1 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 2 Jul 2022 12:26:43 +0100 Subject: util.jwt: Add support for RSA-based algorithms (RS256, PS256) --- util/jwt.lua | 49 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 58888b5d..433a69f6 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -66,8 +66,7 @@ local function new_hmac_algorithm(name, hmac) return { sign = sign, verify = verify, load_key = load_key }; end --- ES*** family -local function new_ecdsa_algorithm(name, c_sign, c_verify) +local function new_crypto_algorithm(name, key_type, c_sign, c_verify, sig_encode, sig_decode) local static_header = new_static_header(name); return { @@ -75,25 +74,27 @@ local function new_ecdsa_algorithm(name, c_sign, c_verify) local encoded_payload = json.encode(payload); local signed = static_header .. b64url(encoded_payload); - local der_sig = c_sign(private_key, signed); - - local r, s = crypto.parse_ecdsa_signature(der_sig); + local signature = c_sign(private_key, signed); + if sig_encode then + signature = sig_encode(signature); + end - return signed.."."..b64url(r..s); + return signed.."."..b64url(signature); end; - verify = function (public_key, blob) + verify = function (public_key, blob) local signed, signature, raw_payload = decode_jwt(blob, name); if not signed then return nil, signature; end -- nil, err - local raw_signature = unb64url(signature); - - local der_sig = crypto.build_ecdsa_signature(raw_signature:sub(1, 32), raw_signature:sub(33, 64)); - if not der_sig then + signature = unb64url(signature); + if sig_decode and signature then + signature = sig_decode(signature); + end + if not signature then return false, "signature-mismatch"; end - local verify_ok = c_verify(public_key, signed, der_sig); + local verify_ok = c_verify(public_key, signed, signature); if not verify_ok then return false, "signature-mismatch"; end @@ -108,21 +109,41 @@ local function new_ecdsa_algorithm(name, c_sign, c_verify) load_public_key = function (public_key_pem) local key = assert(crypto.import_public_pem(public_key_pem)); - assert(key:get_type() == "id-ecPublicKey", "incorrect key type"); + assert(key:get_type() == key_type, "incorrect key type"); return key; end; load_private_key = function (private_key_pem) local key = assert(crypto.import_private_pem(private_key_pem)); - assert(key:get_type() == "id-ecPublicKey", "incorrect key type"); + assert(key:get_type() == key_type, "incorrect key type"); return key; end; }; end +-- RS***, PS*** +local function new_rsa_algorithm(name, c_sign, c_verify) + return new_crypto_algorithm(name, "rsaEncryption", c_sign, c_verify); +end + +-- ES*** +local function new_ecdsa_algorithm(name, c_sign, c_verify) + local function encode_ecdsa_sig(der_sig) + local r, s = crypto.parse_ecdsa_signature(der_sig); + return r..s; + end + + local function decode_ecdsa_sig(jwk_sig) + return crypto.build_ecdsa_signature(jwk_sig:sub(1, 32), jwk_sig:sub(33, 64)); + end + return new_crypto_algorithm(name, "id-ecPublicKey", c_sign, c_verify, encode_ecdsa_sig, decode_ecdsa_sig); +end + local algorithms = { HS256 = new_hmac_algorithm("HS256", hashes.hmac_sha256); ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify); + RS256 = new_rsa_algorithm("RS256", crypto.rsassa_pkcs1_256_sign, crypto.rsassa_pkcs1_256_verify); + PS256 = new_rsa_algorithm("PS256", crypto.rsassa_pss_256_sign, crypto.rsassa_pss_256_verify); }; local function new_signer(algorithm, key_input) -- cgit v1.2.3 From 5316b0005e042f1abeb1ac5fed8715ab65e6752c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 2 Jul 2022 14:59:52 +0100 Subject: util.crypto: More digests for sign/verify, use macros for clarity/consistency --- util/jwt.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 433a69f6..7a05e45d 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -142,8 +142,8 @@ end local algorithms = { HS256 = new_hmac_algorithm("HS256", hashes.hmac_sha256); ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify); - RS256 = new_rsa_algorithm("RS256", crypto.rsassa_pkcs1_256_sign, crypto.rsassa_pkcs1_256_verify); - PS256 = new_rsa_algorithm("PS256", crypto.rsassa_pss_256_sign, crypto.rsassa_pss_256_verify); + RS256 = new_rsa_algorithm("RS256", crypto.rsassa_pkcs1_sha256_sign, crypto.rsassa_pkcs1_sha256_verify); + PS256 = new_rsa_algorithm("PS256", crypto.rsassa_pss_sha256_sign, crypto.rsassa_pss_sha256_verify); }; local function new_signer(algorithm, key_input) -- cgit v1.2.3 From fad58c5ab2fa449aea07eca2a99a1b5e30a22711 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 2 Jul 2022 15:29:04 +0100 Subject: util.jwt: All the algorithms (+ all the tests!) Except 'none'. Not implementing that one. --- util/jwt.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 7a05e45d..7bd98eb1 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -34,9 +34,11 @@ local function new_static_header(algorithm_name) end -- HS*** family -local function new_hmac_algorithm(name, hmac) +local function new_hmac_algorithm(name) local static_header = new_static_header(name); + local hmac = hashes["hmac_sha"..name:sub(-3)]; + local function sign(key, payload) local encoded_payload = json.encode(payload); local signed = static_header .. b64url(encoded_payload); @@ -122,7 +124,11 @@ local function new_crypto_algorithm(name, key_type, c_sign, c_verify, sig_encode end -- RS***, PS*** -local function new_rsa_algorithm(name, c_sign, c_verify) +local rsa_sign_algos = { RS = "rsassa_pkcs1", PS = "rsassa_pss" }; +local function new_rsa_algorithm(name) + local family, digest_bits = name:match("^(..)(...)$"); + local c_sign = crypto[rsa_sign_algos[family].."_sha"..digest_bits.."_sign"]; + local c_verify = crypto[rsa_sign_algos[family].."_sha"..digest_bits.."_verify"]; return new_crypto_algorithm(name, "rsaEncryption", c_sign, c_verify); end @@ -140,10 +146,10 @@ local function new_ecdsa_algorithm(name, c_sign, c_verify) end local algorithms = { - HS256 = new_hmac_algorithm("HS256", hashes.hmac_sha256); + HS256 = new_hmac_algorithm("HS256"), HS384 = new_hmac_algorithm("HS384"), HS512 = new_hmac_algorithm("HS512"); ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify); - RS256 = new_rsa_algorithm("RS256", crypto.rsassa_pkcs1_sha256_sign, crypto.rsassa_pkcs1_sha256_verify); - PS256 = new_rsa_algorithm("PS256", crypto.rsassa_pss_sha256_sign, crypto.rsassa_pss_sha256_verify); + RS256 = new_rsa_algorithm("RS256"), RS384 = new_rsa_algorithm("RS384"), RS512 = new_rsa_algorithm("RS512"); + PS256 = new_rsa_algorithm("PS256"), PS384 = new_rsa_algorithm("PS384"), PS512 = new_rsa_algorithm("PS512"); }; local function new_signer(algorithm, key_input) @@ -167,6 +173,7 @@ end return { new_signer = new_signer; new_verifier = new_verifier; + _algorithms = algorithms; -- Deprecated sign = algorithms.HS256.sign; verify = algorithms.HS256.verify; -- cgit v1.2.3 From 23458111dcf59d6c64e77f3286dbaa752539b77a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 11 Jul 2022 13:28:29 +0100 Subject: util.jwt: Provide built-in token expiry support (defaults to 3600s lifetime) To avoid every user of the library needing to add and verify expiry info, this is now handled by util.jwt itself (if not overridden or disabled). Issuing tokens that are valid forever is bad practice and rarely desired, and the default token lifetime is now 3600s (1 hour). --- util/jwt.lua | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 7bd98eb1..3501d9f2 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -152,21 +152,46 @@ local algorithms = { PS256 = new_rsa_algorithm("PS256"), PS384 = new_rsa_algorithm("PS384"), PS512 = new_rsa_algorithm("PS512"); }; -local function new_signer(algorithm, key_input) +local function new_signer(algorithm, key_input, options) local impl = assert(algorithms[algorithm], "Unknown JWT algorithm: "..algorithm); local key = (impl.load_private_key or impl.load_key)(key_input); local sign = impl.sign; + local default_ttl = (options and options.default_ttl) or 3600; return function (payload) + local issued_at; + if not payload.iat then + issued_at = os.time(); + payload.iat = issued_at; + end + if not payload.exp then + payload.exp = (issued_at or os.time()) + default_ttl; + end return sign(key, payload); end end -local function new_verifier(algorithm, key_input) +local function new_verifier(algorithm, key_input, options) local impl = assert(algorithms[algorithm], "Unknown JWT algorithm: "..algorithm); local key = (impl.load_public_key or impl.load_key)(key_input); local verify = impl.verify; + local check_expiry = not (options and options.accept_expired); + local claim_verifier = options and options.claim_verifier; return function (token) - return verify(key, token); + local ok, payload = verify(key, token); + if ok then + local expires_at = check_expiry and payload.exp; + if expires_at then + if type(expires_at) ~= "number" then + return nil, "invalid-expiry"; + elseif expires_at < os.time() then + return nil, "token-expired"; + end + end + if claim_verifier and not claim_verifier(payload) then + return nil, "incorrect-claims"; + end + end + return ok, payload; end end -- cgit v1.2.3 From d2ede10c5d4ed029731f2b431aea3b35da2dd999 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 11 Jul 2022 13:42:08 +0100 Subject: util.jwt: Consolidate payload parsing, ensure it's always a valid object --- util/jwt.lua | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 3501d9f2..53825236 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -33,6 +33,16 @@ local function new_static_header(algorithm_name) return b64url('{"alg":"'..algorithm_name..'","typ":"JWT"}') .. '.'; end +local function decode_raw_payload(raw_payload) + local payload, err = json.decode(unb64url(raw_payload)); + if err ~= nil then + return nil, "json-decode-error"; + elseif type(payload) ~= "table" then + return nil, "invalid-payload-type"; + end + return true, payload; +end + -- HS*** family local function new_hmac_algorithm(name) local static_header = new_static_header(name); @@ -53,11 +63,8 @@ local function new_hmac_algorithm(name) if not secure_equals(b64url(hmac(key, signed)), signature) then return false, "signature-mismatch"; end - local payload, err = json.decode(unb64url(raw_payload)); - if err ~= nil then - return nil, "json-decode-error"; - end - return true, payload; + + return decode_raw_payload(raw_payload); end local function load_key(key) @@ -101,12 +108,7 @@ local function new_crypto_algorithm(name, key_type, c_sign, c_verify, sig_encode return false, "signature-mismatch"; end - local payload, err = json.decode(unb64url(raw_payload)); - if err ~= nil then - return nil, "json-decode-error"; - end - - return true, payload; + return decode_raw_payload(raw_payload); end; load_public_key = function (public_key_pem) -- cgit v1.2.3 From ff54cc5bcfd5fc9ec23f8d974f0c46363f9bb35a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 11 Jul 2022 13:43:01 +0100 Subject: util.jwt: Add new init() convenience method to obtain both signer and verifier --- util/jwt.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 53825236..5c74850a 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -197,9 +197,15 @@ local function new_verifier(algorithm, key_input, options) end end +local function init(algorithm, private_key, public_key, options) + return new_signer(algorithm, private_key, options), new_verifier(algorithm, public_key or private_key, options); +end + return { + init = init; new_signer = new_signer; new_verifier = new_verifier; + -- Exported mainly for tests _algorithms = algorithms; -- Deprecated sign = algorithms.HS256.sign; -- cgit v1.2.3 From 8695a72a668fa38f1df64653508c360534e5e3db Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 29 Sep 2022 23:15:39 +0100 Subject: util.crypto, util.jwt: Generate consistent signature sizes (via padding) This fixes the signature parsing and building to work correctly. Sometimes a signature was one or two bytes too short, and needed to be padded. OpenSSL can do this for us. --- util/jwt.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 5c74850a..1c7daf12 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -135,21 +135,21 @@ local function new_rsa_algorithm(name) end -- ES*** -local function new_ecdsa_algorithm(name, c_sign, c_verify) +local function new_ecdsa_algorithm(name, c_sign, c_verify, sig_bytes) local function encode_ecdsa_sig(der_sig) - local r, s = crypto.parse_ecdsa_signature(der_sig); + local r, s = crypto.parse_ecdsa_signature(der_sig, sig_bytes); return r..s; end local function decode_ecdsa_sig(jwk_sig) - return crypto.build_ecdsa_signature(jwk_sig:sub(1, 32), jwk_sig:sub(33, 64)); + return crypto.build_ecdsa_signature(jwk_sig:sub(1, sig_bytes), jwk_sig:sub(sig_bytes+1, sig_bytes*2)); end return new_crypto_algorithm(name, "id-ecPublicKey", c_sign, c_verify, encode_ecdsa_sig, decode_ecdsa_sig); end local algorithms = { HS256 = new_hmac_algorithm("HS256"), HS384 = new_hmac_algorithm("HS384"), HS512 = new_hmac_algorithm("HS512"); - ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify); + ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify, 32); RS256 = new_rsa_algorithm("RS256"), RS384 = new_rsa_algorithm("RS384"), RS512 = new_rsa_algorithm("RS512"); PS256 = new_rsa_algorithm("PS256"), PS384 = new_rsa_algorithm("PS384"), PS512 = new_rsa_algorithm("PS512"); }; -- cgit v1.2.3 From 5ca81b0e3bcef02ebf6e518e10a855c6ed3efd7f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 29 Sep 2022 23:17:42 +0100 Subject: util.jwt: Add support for ES512 (+ tests) --- util/jwt.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 1c7daf12..0c878efb 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -150,6 +150,7 @@ end local algorithms = { HS256 = new_hmac_algorithm("HS256"), HS384 = new_hmac_algorithm("HS384"), HS512 = new_hmac_algorithm("HS512"); ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify, 32); + ES512 = new_ecdsa_algorithm("ES512", crypto.ecdsa_sha512_sign, crypto.ecdsa_sha512_verify, 66); RS256 = new_rsa_algorithm("RS256"), RS384 = new_rsa_algorithm("RS384"), RS512 = new_rsa_algorithm("RS512"); PS256 = new_rsa_algorithm("PS256"), PS384 = new_rsa_algorithm("PS384"), PS512 = new_rsa_algorithm("PS512"); }; -- cgit v1.2.3 From e7bfb40a324c806c350f4c87cd41e3b83033377e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 30 Sep 2022 20:38:31 +0100 Subject: util.jwt: More robust ECDSA signature parsing, fail early on unexpected length --- util/jwt.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'util/jwt.lua') diff --git a/util/jwt.lua b/util/jwt.lua index 0c878efb..42a9f7f2 100644 --- a/util/jwt.lua +++ b/util/jwt.lua @@ -141,8 +141,12 @@ local function new_ecdsa_algorithm(name, c_sign, c_verify, sig_bytes) return r..s; end + local expected_sig_length = sig_bytes*2; local function decode_ecdsa_sig(jwk_sig) - return crypto.build_ecdsa_signature(jwk_sig:sub(1, sig_bytes), jwk_sig:sub(sig_bytes+1, sig_bytes*2)); + if #jwk_sig ~= expected_sig_length then + return nil; + end + return crypto.build_ecdsa_signature(jwk_sig:sub(1, sig_bytes), jwk_sig:sub(sig_bytes+1)); end return new_crypto_algorithm(name, "id-ecPublicKey", c_sign, c_verify, encode_ecdsa_sig, decode_ecdsa_sig); end -- cgit v1.2.3