Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) add PS256 and PS512 support for signing and verification #57

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 51 additions & 28 deletions lib/resty/evp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -316,14 +316,54 @@ local function _create_evp_ctx(self, encrypt)
return self.ctx
end

local evp_pkey_ctx_ptr_ptr_ct = ffi.typeof('EVP_PKEY_CTX*[1]')

local function _create_sign_verify_ctx(self, finit_ex, fint, md_alg)

local pkey_ctx, ppkey_ctx
if self.padding then
pkey_ctx = _C.EVP_PKEY_CTX_new(self.evp_pkey, nil)
if pkey_ctx == nil then
return nil, "pkey:_create_sign_verify_ctx EVP_PKEY_CTX_new()"
end
ffi_gc(pkey_ctx, _C.EVP_PKEY_CTX_free)

ppkey_ctx = evp_pkey_ctx_ptr_ptr_ct()
ppkey_ctx[0] = pkey_ctx
end

local md_ctx = ctx_new()
if md_ctx == nil then
return nil, "pkey:_create_sign_verify_ctx: EVP_MD_CTX_new() failed"
end
ctx_free(md_ctx)

if finit_ex(md_ctx, md_alg, nil) ~= 1 then
return nil, "pkey:_create_sign_verify_ctx: Init_ex() failed"
end

if fint(md_ctx, ppkey_ctx, md_alg, nil, self.evp_pkey) ~= 1 then
return nil, "pkey:_create_sign_verify_ctx: Init failed"
end

if ppkey_ctx and self.padding == CONST.RSA_PKCS1_PSS_PADDING then
if _C.EVP_PKEY_CTX_ctrl(ppkey_ctx[0], CONST.EVP_PKEY_RSA, -1, CONST.EVP_PKEY_CTRL_RSA_PADDING, self.padding, nil) <= 0 then
return nil, "pkey:_create_sign_verify_ctx: EVP_PKEY_CTX_ctrl() failed"
end
end

return md_ctx
end

local RSASigner = {algo="RSA"}
_M.RSASigner = RSASigner

--- Create a new RSASigner
-- @param pem_private_key A private key string in PEM format
-- @param password password for the private key (if required)
-- @returns RSASigner, err_string
function RSASigner.new(self, pem_private_key, password)
function RSASigner.new(self, pem_private_key, password, padding)
self.padding = padding
return _new_key (
self,
{
Expand All @@ -342,29 +382,20 @@ function RSASigner.sign(self, message, digest_name)
local buf = ffi_new("unsigned char[?]", 1024)
local len = ffi_new("size_t[1]", 1024)

local ctx = ctx_new()
if ctx == nil then
return _err()
end
ctx_free(ctx)

local md = _C.EVP_get_digestbyname(digest_name)
if md == nil then
return _err()
end

if _C.EVP_DigestInit_ex(ctx, md, nil) ~= 1 then
local md_ctx, err = _create_sign_verify_ctx(self, _C.EVP_DigestInit_ex, _C.EVP_DigestSignInit, md)
if err then
return _err()
end

local ret = _C.EVP_DigestSignInit(ctx, nil, md, nil, self.evp_pkey)
if ret ~= 1 then
return _err()
end
if _C.EVP_DigestUpdate(ctx, message, #message) ~= 1 then
if _C.EVP_DigestUpdate(md_ctx, message, #message) ~= 1 then
return _err()
end
if _C.EVP_DigestSignFinal(ctx, buf, len) ~= 1 then
if _C.EVP_DigestSignFinal(md_ctx, buf, len) ~= 1 then
return _err()
end
return ffi_string(buf, len[0]), nil
Expand Down Expand Up @@ -438,12 +469,13 @@ _M.RSAVerifier = RSAVerifier
--- Create a new RSAVerifier
-- @param key_source An instance of Cert or PublicKey used for verification
-- @returns RSAVerifier, error_string
function RSAVerifier.new(self, key_source)
function RSAVerifier.new(self, key_source, padding)
if not key_source then
return nil, "You must pass in an key_source for a public key"
end
local evp_public_key = key_source.public_key
self.evp_pkey = evp_public_key
self.padding = padding
return self, nil
end

Expand All @@ -458,26 +490,17 @@ function RSAVerifier.verify(self, message, sig, digest_name)
return _err(false)
end

local ctx = ctx_new()
if ctx == nil then
return _err(false)
end
ctx_free(ctx)

if _C.EVP_DigestInit_ex(ctx, md, nil) ~= 1 then
local md_ctx, err = _create_sign_verify_ctx(self, _C.EVP_DigestInit_ex, _C.EVP_DigestVerifyInit, md)
if err then
return _err(false)
end

local ret = _C.EVP_DigestVerifyInit(ctx, nil, md, nil, self.evp_pkey)
if ret ~= 1 then
return _err(false)
end
if _C.EVP_DigestUpdate(ctx, message, #message) ~= 1 then
if _C.EVP_DigestUpdate(md_ctx, message, #message) ~= 1 then
return _err(false)
end
local sig_bin = ffi_new("unsigned char[?]", #sig)
ffi_copy(sig_bin, sig, #sig)
if _C.EVP_DigestVerifyFinal(ctx, sig_bin, #sig) == 1 then
if _C.EVP_DigestVerifyFinal(md_ctx, sig_bin, #sig) == 1 then
return true, nil
else
return false, "Verification failed"
Expand Down
27 changes: 19 additions & 8 deletions lib/resty/jwt.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ local str_const = {
HS256 = "HS256",
HS512 = "HS512",
RS256 = "RS256",
RS512 = "RS512",
PS256 = "PS256",
PS512 = "PS512",
ES256 = "ES256",
ES512 = "ES512",
RS512 = "RS512",
A128CBC_HS256 = "A128CBC-HS256",
A128CBC_HS256_CIPHER_MODE = "aes-128-cbc",
A256CBC_HS512 = "A256CBC-HS512",
Expand Down Expand Up @@ -564,14 +566,19 @@ function _M.sign(self, secret_key, jwt_obj)
elseif alg == str_const.HS512 then
local secret_str = get_secret_str(secret_key, jwt_obj)
signature = hmac:new(secret_str, hmac.ALGOS.SHA512):final(message)
elseif alg == str_const.RS256 or alg == str_const.RS512 then
local signer, err = evp.RSASigner:new(secret_key)
elseif alg == str_const.RS256 or alg == str_const.RS512 or alg == str_const.PS256 or alg == str_const.PS512 then
local signer, err
if alg == str_const.PS256 or alg == str_const.PS512 then
signer, err = evp.RSASigner:new(secret_key, nil, evp.CONST.RSA_PKCS1_PSS_PADDING)
else
signer, err = evp.RSASigner:new(secret_key)
end
if not signer then
error({reason="signer error: " .. err})
end
if alg == str_const.RS256 then
if alg == str_const.RS256 or alg == str_const.PS256 then
signature = signer:sign(message, evp.CONST.SHA256_DIGEST)
elseif alg == str_const.RS512 then
elseif alg == str_const.RS512 or alg == str_const.PS512 then
signature = signer:sign(message, evp.CONST.SHA512_DIGEST)
end
elseif alg == str_const.ES256 or alg == str_const.ES512 then
Expand Down Expand Up @@ -847,7 +854,9 @@ function _M.verify_jwt_obj(self, secret, jwt_obj, ...)
-- signature check
jwt_obj[str_const.reason] = "signature mismatch: " .. jwt_obj[str_const.signature]
end
elseif alg == str_const.RS256 or alg == str_const.RS512 or alg == str_const.ES256 or alg == str_const.ES512 then
elseif alg == str_const.RS256 or alg == str_const.RS512 or
alg == str_const.ES256 or alg == str_const.ES512 or
alg == str_const.PS256 or alg == str_const.PS512 then
local cert, err
if self.trusted_certs_file ~= nil then
local cert_str = extract_certificate(jwt_obj, self.x5u_content_retriever)
Expand Down Expand Up @@ -882,6 +891,8 @@ function _M.verify_jwt_obj(self, secret, jwt_obj, ...)
local verifier = ''
if alg == str_const.RS256 or alg == str_const.RS512 then
verifier = evp.RSAVerifier:new(cert)
elseif alg == str_const.PS256 or alg == str_const.PS512 then
verifier = evp.RSAVerifier:new(cert, evp.CONST.RSA_PKCS1_PSS_PADDING)
elseif alg == str_const.ES256 or alg == str_const.ES512 then
verifier = evp.ECVerifier:new(cert)
end
Expand All @@ -906,9 +917,9 @@ function _M.verify_jwt_obj(self, secret, jwt_obj, ...)
local verified = false
err = "verify error: reason unknown"

if alg == str_const.RS256 or alg == str_const.ES256 then
if alg == str_const.RS256 or alg == str_const.ES256 or alg == str_const.PS256 then
verified, err = verifier:verify(message, sig, evp.CONST.SHA256_DIGEST)
elseif alg == str_const.RS512 or alg == str_const.ES512 then
elseif alg == str_const.RS512 or alg == str_const.ES512 or alg == str_const.PS512 then
verified, err = verifier:verify(message, sig, evp.CONST.SHA512_DIGEST)
end
if not verified then
Expand Down
76 changes: 75 additions & 1 deletion t/load-verify.t
Original file line number Diff line number Diff line change
Expand Up @@ -803,4 +803,78 @@ true
everything is awesome~ :p
test
--- no_error_log
[error]
[error]

=== TEST 26: Verify valid PS256 signed jwt using a rsa public key
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua '
local jwt = require "resty.jwt"

local public_key = [[
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvE+myl5JR22jRgb2+pv4
h29IH26WdenM6FsJ7Ks2nZDdSfL8S0ahp8Tk/nrC7Zi/MMUkNeZlkLFkS5mHeeyw
v5UpGJqbP3rKnsiP/Uomt8NJufusv/HWZSpI784rGzkHy+FY5Vs/nexw9K5os/mm
wZqjsEtxZU5kfg9Ye+VCtXW3ArXstCEu2Gd93C91NEX//g67ahAI3ii6TAsml8VA
Qcl2+7pK6HPgAmcPECnhhfNaqEPIcdxOToAymCG3SLB54MIyN3kvOFjK0Ztj82U0
qqibxZcAAw2yK07kz2sSF8VtM5B3mgE9DFpskezhSIRc+nuhHoyTn8pscisa2JNq
dQIDAQAB
-----END PUBLIC KEY-----
]]

jwt:set_alg_whitelist({ PS256 = 1 })
local jwt_token = "eyJraWQiOiJlYWY3YzFkNS0xMzI1LTQ3NmMtOTdlYi03N2VkNGEzMDNhZTciLCJhbGciOiJQUzI1NiJ9.ew0KICAiaXNzIjogInRlc3QiLA0KICAiaWF0IjogMTUxNjIzOTAyMg0KfQ.n2i-wYB_XOTJSo7oIAVq_zxm5BGG7cfPTwo9ZD0agoJvLQy-D0btxkhaNJj7lJcAtxi3ffpYB2kHVcUa7YKNO6szNU1AC4r9iIgQn1wjgfLcmxVxnOvHt7EUwn6fVoNxNU7AX-s-1eMaAuIPxretYGvFFfc3kXJYWdfQcr_4LbtlG3EDg-WUetJ75JmzfZPW963TdUWZ17uyPf8TjwLDpJl0OyPDAvo-sh4J2ySj43VVNpEhR50tqE2FrHM6mz1d9MliNU9HbUWkEdbpLwmDrHgfpaaKyWMKCWkxiOpcxivfw9CmvrYciQg1VWYDp149yvEUOLjytZ4NXRVSbSKtnw"

local jwt_obj = jwt:verify(public_key, jwt_token)
ngx.say(jwt_obj["verified"])
ngx.say(jwt_obj["reason"])
ngx.say(jwt_obj["payload"]["iss"])
';
}
--- request
GET /t
--- response_body
true
everything is awesome~ :p
test
--- no_error_log
[error]

=== TEST 25: Verify valid PS512 signed jwt using a rsa public key
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua '
local jwt = require "resty.jwt"

local public_key = [[
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqy2xX7I8txBV5WXLebw/
pVzbnDNQ4dbR+Cqe0VHB6H9QIztM36AN6a8WX+5er71lsSEHoz5ivTOju5INBMft
44fD+UAdWWLtFq17PX2EGNLkO0yQSNh+nb6MCym0Q+wShyMBPWfJSPv6eU4Ixx1r
DfyNMkZ2mhoWqAIahCI+Sz7DMJ60h4k7DRWQX9kfQbMC+0suyNolAiBOYlHdqIIC
t6FtpnTaLDHNx5ExavD/wtcdycj3z/G+zUec4hVI5j/DUdkg/9IwkzUvC9HV4Qek
pdWoqSSrJKHruf0nqJ15vupBgalbuRePJg7p/9XjtTPP9Tm9g99Ikwqf4eIrb/Uo
AQIDAQAB
-----END PUBLIC KEY-----
]]

jwt:set_alg_whitelist({ PS512 = 1 })
local jwt_token = "eyJraWQiOiI4MTlmMmY5OS0xNDY4LTQyYmItOGIyMy00ZTkyOWUwNWJjMDQiLCJhbGciOiJQUzUxMiJ9.ew0KICAiaXNzIjogInRlc3QiLA0KICAiaWF0IjogMTUxNjIzOTAyMg0KfQ.hHoWVr_Slu1xpe4ehqsvmeIqsKxJVS6MLV-58rw08zQvhIVjzoUjRr5QkdakxA_0NF6ubJUztprxfclFfBNO1tLAMv1BLujXdSTgWDxG9Vr4hhDRIJY53xrUY6ozIPBsZKYPRJQjoOMk3EotI8vPRNYyj778EmwT3nNr1W9kRjpKa9lKmcAIwSDeYN_7BrvJk7oqpRyQDfEkcOaEsVwgvgz2lGMzoA-6NXzLplG6JC8I7Ncq3ympV80e0TZ3YnNR_74If_yIh57M0EOl8c4C0YWGNaFTFXw18zpRgHS6svqpZ3LNXM8O-yBPaixfV1Dr9qJvA6Q_u8okDY618S_iqw"

local jwt_obj = jwt:verify(public_key, jwt_token)
ngx.say(jwt_obj["verified"])
ngx.say(jwt_obj["reason"])
ngx.say(jwt_obj["payload"]["iss"])
';
}
--- request
GET /t
--- response_body
true
everything is awesome~ :p
test
--- no_error_log
[error]
Loading