Skip to content

Commit

Permalink
feat: add jwe decrypt plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
fishioon committed Sep 25, 2023
1 parent 88da2ec commit 3c611f3
Show file tree
Hide file tree
Showing 3 changed files with 421 additions and 0 deletions.
229 changes: 229 additions & 0 deletions apisix/plugins/jwe-decrypt.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local core = require("apisix.core")
local consumer_mod = require("apisix.consumer")
local base64 = require("ngx.base64")
local ngx = ngx
local ngx_time = ngx.time
local sub_str = string.sub
local table_insert = table.insert
local table_concat = table.concat
local ngx_re_gmatch = ngx.re.gmatch
local plugin_name = "jwe-decrypt"
local pcall = pcall
local cipher = assert(require("resty.openssl.cipher").new("aes-256-gcm"))

local schema = {
type = "object",
properties = {
header = {
type = "string",
default = "Authorization"
},
forward_header = {
type = "string",
default = "Authorization"
},
strict = {
type = "boolean",
default = true
}
},
}

local consumer_schema = {
type = "object",
-- can't use additionalProperties with dependencies
properties = {
key = { type = "string" },
secret = { type = "string" },
base64_secret = { type = "boolean" },
},
required = { "key", "secret" },
}


local _M = {
version = 0.1,
priority = 2510,
type = 'auth',
name = plugin_name,
schema = schema,
consumer_schema = consumer_schema
}


function _M.check_schema(conf, schema_type)
core.log.info("input conf: ", core.json.delay_encode(conf))

local ok, err
if schema_type == core.schema.TYPE_CONSUMER then
ok, err = core.schema.check(consumer_schema, conf)
else
return core.schema.check(schema, conf)
end

if not ok then
return false, err
end

return true
end

local function get_secret(conf)
local secret = conf.secret

if conf.base64_secret then
return base64.decode_base64(secret)
end

return secret
end

local function load_jwe_token(jwe_token)
local o = {}
o.header, _, o.iv, o.ciphertext, o.tag = jwe_token:match("(.-)%.(.-)%.(.-)%.(.-)%.(.*)")
local dec = base64.decode_base64url
o.header_obj = core.json.decode(dec(o.header))
o.valid = true
return o
end

local function jwe_decrypt_with_obj(o, consumer)
local secret = get_secret(consumer.auth_conf)
local dec = base64.decode_base64url
return cipher:decrypt(secret, dec(o.iv), dec(o.ciphertext), false, o.header, dec(o.tag))
end

local function jwe_encrypt(o, consumer)
local secret = get_secret(consumer.auth_conf)
local dec = base64.decode_base64url
local enc = base64.encode_base64url
core.log.error("jwe-encrypt: ", secret, o.iv, o.plaintext, o.header)
o.ciphertext = cipher:encrypt(secret, o.iv, o.plaintext, false, o.header)
o.tag = cipher:get_aead_tag()
return o.header .. ".." .. enc(o.iv) .. "." .. enc(o.ciphertext) .. "." .. enc(o.tag)
end


local function get_consumer(key)
local consumer_conf = consumer_mod.plugin(plugin_name)
if not consumer_conf then
return nil
end
local consumers = consumer_mod.consumers_kv(plugin_name, consumer_conf, "key")
core.log.info("consumers: ", core.json.delay_encode(consumers))
return consumers[key]
end

local function fetch_jwe_token(conf, ctx)
local token = core.request.header(ctx, conf.header)
if token then
local prefix = sub_str(token, 1, 7)
if prefix == 'Bearer ' or prefix == 'bearer ' then
return sub_str(token, 8)
end

return token
end
end

function _M.rewrite(conf, ctx)
-- fetch token and hide credentials if necessary
local jwe_token, err = fetch_jwe_token(conf, ctx)
if not jwe_token and conf.strict then
core.log.info("failed to fetch JWE token: ", err)
return 403, { message = "missing JWE token in request" }
end

local jwe_obj = load_jwe_token(jwe_token)

if not jwe_obj.valid then
return 400, { message = "JWE token invalid" }
end

if not jwe_obj.header_obj.kid then
return 400, { message = "missing kid in JWE token" }
end

local consumer = get_consumer(jwe_obj.header_obj.kid)
if not consumer then
return 400, { message = "invalid kid in JWE token" }
end

local plaintext, err = jwe_decrypt_with_obj(jwe_obj, consumer)
if err ~= nil then
return 400, { message = "failed to decrypt JWE token" }
end
core.request.set_header(ctx, conf.forward_header, plaintext)
end

local function gen_token()
local args = core.request.get_uri_args()
if not args or not args.key then
return core.response.exit(400)
end

local key = args.key
local payload = args.payload
if payload then
payload = ngx.unescape_uri(payload)
end

local consumer = get_consumer(key)
if not consumer then
return core.response.exit(404)
end

core.log.info("consumer: ", core.json.delay_encode(consumer))

local iv = args.iv
if not iv then
-- TODO: random bytes
iv = "123456789012"
end

local obj = {
iv = iv,
plaintext = payload,
header_obj = {
kid = key,
alg = "dir",
enc = "A256GCM",
},
}
obj.header = base64.encode_base64url(core.json.encode(obj.header_obj))
local jwe_token = jwe_encrypt(obj, consumer)
if jwe_token then
return core.response.exit(200, jwe_token)
end

return core.response.exit(404)
end


function _M.api()
return {
{
methods = { "GET" },
uri = "/apisix/plugin/jwe/encrypt",
handler = gen_token,
}
}
end

return _M
1 change: 1 addition & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ plugins: # plugin list (sorted by priority)
- uri-blocker # priority: 2900
- request-validation # priority: 2800
- chaitin-waf # priority: 2700
- jwe-decrypt # priority: 2600
- openid-connect # priority: 2599
- cas-auth # priority: 2597
- authz-casbin # priority: 2560
Expand Down
Loading

0 comments on commit 3c611f3

Please sign in to comment.