197 lines
4.3 KiB
Lua
197 lines
4.3 KiB
Lua
local b64 = require "ngx.base64"
|
|
local cjson = require "cjson.safe"
|
|
local file = require "pl.file"
|
|
local path = require "pl.path"
|
|
local cipher = require "resty.openssl.cipher"
|
|
local digest = require "resty.openssl.digest"
|
|
local rand = require "resty.openssl.rand"
|
|
|
|
|
|
local fmt = string.format
|
|
local sub = string.sub
|
|
local type = type
|
|
local pairs = pairs
|
|
local shallow_copy = require("kong.tools.table").shallow_copy
|
|
|
|
|
|
local CIPHER_ALG = "aes-256-gcm"
|
|
local DIGEST_ALG = "sha256"
|
|
local IV_SIZE = 12
|
|
local TAG_SIZE = 16
|
|
local AAD = fmt("%s|%s", CIPHER_ALG, DIGEST_ALG)
|
|
|
|
|
|
local function read_key_data(key_data_path)
|
|
if not path.exists(key_data_path) then
|
|
return nil, fmt("failed to read key data (%s): file not found", key_data_path)
|
|
end
|
|
|
|
local key_data, err = file.read(key_data_path, true)
|
|
if not key_data then
|
|
return nil, fmt("failed to read key data file: %s", err)
|
|
end
|
|
|
|
return key_data
|
|
end
|
|
|
|
|
|
local function hash_key_data(key_data)
|
|
local hash, err = digest.new(DIGEST_ALG)
|
|
if not hash then
|
|
return nil, fmt("unable to initialize digest (%s)", err)
|
|
end
|
|
|
|
local ok
|
|
ok, err = hash:update(key_data)
|
|
if not ok then
|
|
return nil, fmt("unable to update digest (%s)", err)
|
|
end
|
|
|
|
local key
|
|
key, err = hash:final()
|
|
if not key then
|
|
return nil, fmt("unable to create digest (%s)", err)
|
|
end
|
|
|
|
return key
|
|
end
|
|
|
|
|
|
local function extract(conf)
|
|
local refs = conf["$refs"]
|
|
if not refs or type(refs) ~= "table" then
|
|
return
|
|
end
|
|
|
|
local secrets = {}
|
|
for k in pairs(refs) do
|
|
secrets[k] = shallow_copy(conf[k])
|
|
end
|
|
|
|
return secrets
|
|
end
|
|
|
|
|
|
local function encrypt(plaintext, key_data)
|
|
local key, err = hash_key_data(key_data)
|
|
if not key then
|
|
return nil, err
|
|
end
|
|
|
|
local iv
|
|
iv, err = rand.bytes(IV_SIZE)
|
|
if not iv then
|
|
return nil, fmt("unable to generate initialization vector (%s)", err)
|
|
end
|
|
|
|
local cip, err = cipher.new(CIPHER_ALG)
|
|
if not cip then
|
|
return nil, fmt("unable to initialize cipher (%s)", err)
|
|
end
|
|
|
|
local ciphertext
|
|
ciphertext, err = cip:encrypt(key, iv, plaintext, false, AAD)
|
|
if not ciphertext then
|
|
return nil, fmt("unable to encrypt (%s)", err)
|
|
end
|
|
|
|
local tag
|
|
tag, err = cip:get_aead_tag(TAG_SIZE)
|
|
if not tag then
|
|
return nil, fmt("unable to get authentication tag (%s)", err)
|
|
end
|
|
|
|
return iv .. tag .. ciphertext
|
|
end
|
|
|
|
|
|
local function decrypt(ciphertext, key_data)
|
|
local key, err = hash_key_data(key_data)
|
|
if not key then
|
|
return nil, err
|
|
end
|
|
|
|
local iv = sub(ciphertext, 1, IV_SIZE)
|
|
local tag = sub(ciphertext, IV_SIZE + 1, IV_SIZE + TAG_SIZE)
|
|
|
|
ciphertext = sub(ciphertext, IV_SIZE + TAG_SIZE + 1)
|
|
|
|
local cip, err = cipher.new(CIPHER_ALG)
|
|
if not cip then
|
|
return nil, fmt("unable to initialize cipher (%s)", err)
|
|
end
|
|
|
|
local plaintext
|
|
plaintext, err = cip:decrypt(key, iv, ciphertext, false, AAD, tag)
|
|
if not plaintext then
|
|
return nil, fmt("unable to decrypt (%s)", err)
|
|
end
|
|
|
|
return plaintext
|
|
end
|
|
|
|
|
|
local function serialize(input, key_data_path)
|
|
local output, err = cjson.encode(input)
|
|
if not output then
|
|
return nil, fmt("failed to json encode process secrets: %s", err)
|
|
end
|
|
|
|
if key_data_path then
|
|
local key_data
|
|
key_data, err = read_key_data(key_data_path)
|
|
if not key_data then
|
|
return nil, err
|
|
end
|
|
|
|
output, err = encrypt(output, key_data)
|
|
if not output then
|
|
return nil, fmt("failed to encrypt process secrets: %s", err)
|
|
end
|
|
end
|
|
|
|
output, err = b64.encode_base64url(output)
|
|
if not output then
|
|
return nil, fmt("failed to base64 encode process secrets: %s", err)
|
|
end
|
|
|
|
return output
|
|
end
|
|
|
|
|
|
local function deserialize(input, key_data_path)
|
|
local output, err = b64.decode_base64url(input)
|
|
if not output then
|
|
return nil, fmt("failed to base64 decode process secrets: %s", err)
|
|
end
|
|
|
|
if key_data_path then
|
|
local key_data
|
|
key_data, err = read_key_data(key_data_path)
|
|
if not key_data then
|
|
return nil, err
|
|
end
|
|
|
|
output, err = decrypt(output, key_data)
|
|
if not output then
|
|
return nil, fmt("failed to decrypt process secrets: %s", err)
|
|
end
|
|
end
|
|
|
|
output, err = cjson.decode(output)
|
|
if not output then
|
|
return nil, fmt("failed to json decode process secrets: %s", err)
|
|
end
|
|
|
|
return output
|
|
end
|
|
|
|
|
|
return {
|
|
extract = extract,
|
|
encrypt = encrypt,
|
|
decrypt = decrypt,
|
|
serialize = serialize,
|
|
deserialize = deserialize,
|
|
}
|