diff options
author | Fiete Ostkamp <Fiete.Ostkamp@telekom.de> | 2023-04-14 11:59:32 +0000 |
---|---|---|
committer | Fiete Ostkamp <Fiete.Ostkamp@telekom.de> | 2023-04-14 11:59:32 +0000 |
commit | d68841d9f75636575cd778838a8ceea5fd5aada3 (patch) | |
tree | 778c84203ed9bfa4dc1c8234e4e2cf60da6ebd8c /server/resty/openssl/cipher.lua | |
parent | 42af09588f1f839b9ab36356f02f34c89559bcfa (diff) |
Upload ui
Issue-ID: PORTAL-1084
Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de>
Change-Id: Id0c94859a775094e67b0bb9c91ca5e776a08c068
Diffstat (limited to 'server/resty/openssl/cipher.lua')
-rw-r--r-- | server/resty/openssl/cipher.lua | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/server/resty/openssl/cipher.lua b/server/resty/openssl/cipher.lua new file mode 100644 index 0000000..693ac09 --- /dev/null +++ b/server/resty/openssl/cipher.lua @@ -0,0 +1,300 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_str = ffi.string +local ffi_cast = ffi.cast + +require "resty.openssl.include.evp.cipher" +local evp_macro = require "resty.openssl.include.evp" +local ctypes = require "resty.openssl.auxiliary.ctypes" +local ctx_lib = require "resty.openssl.ctx" +local format_error = require("resty.openssl.err").format_error +local OPENSSL_10 = require("resty.openssl.version").OPENSSL_10 +local OPENSSL_11_OR_LATER = require("resty.openssl.version").OPENSSL_11_OR_LATER +local OPENSSL_3X = require("resty.openssl.version").OPENSSL_3X + +local uchar_array = ctypes.uchar_array +local void_ptr = ctypes.void_ptr +local ptr_of_int = ctypes.ptr_of_int +local uchar_ptr = ctypes.uchar_ptr + +local _M = {} +local mt = {__index = _M} + +local cipher_ctx_ptr_ct = ffi.typeof('EVP_CIPHER_CTX*') + +local out_length = ptr_of_int() +-- EVP_MAX_BLOCK_LENGTH is 32, we give it a 64 to be future proof +local out_buffer = ctypes.uchar_array(1024 + 64) + +function _M.new(typ, properties) + if not typ then + return nil, "cipher.new: expect type to be defined" + end + + local ctx + if OPENSSL_11_OR_LATER then + ctx = C.EVP_CIPHER_CTX_new() + ffi_gc(ctx, C.EVP_CIPHER_CTX_free) + elseif OPENSSL_10 then + ctx = ffi.new('EVP_CIPHER_CTX') + C.EVP_CIPHER_CTX_init(ctx) + ffi_gc(ctx, C.EVP_CIPHER_CTX_cleanup) + end + if ctx == nil then + return nil, "cipher.new: failed to create EVP_CIPHER_CTX" + end + + local ctyp + if OPENSSL_3X then + ctyp = C.EVP_CIPHER_fetch(ctx_lib.get_libctx(), typ, properties) + else + ctyp = C.EVP_get_cipherbyname(typ) + end + local err_new = string.format("cipher.new: invalid cipher type \"%s\"", typ) + if ctyp == nil then + return nil, format_error(err_new) + end + + local code = C.EVP_CipherInit_ex(ctx, ctyp, nil, "", nil, -1) + if code ~= 1 then + return nil, format_error(err_new) + end + + return setmetatable({ + ctx = ctx, + algo = ctyp, + initialized = false, + block_size = tonumber(OPENSSL_3X and C.EVP_CIPHER_CTX_get_block_size(ctx) + or C.EVP_CIPHER_CTX_block_size(ctx)), + key_size = tonumber(OPENSSL_3X and C.EVP_CIPHER_CTX_get_key_length(ctx) + or C.EVP_CIPHER_CTX_key_length(ctx)), + iv_size = tonumber(OPENSSL_3X and C.EVP_CIPHER_CTX_get_iv_length(ctx) + or C.EVP_CIPHER_CTX_iv_length(ctx)), + }, mt), nil +end + +function _M.istype(l) + return l and l.ctx and ffi.istype(cipher_ctx_ptr_ct, l.ctx) +end + +function _M:get_provider_name() + if not OPENSSL_3X then + return false, "cipher:get_provider_name is not supported" + end + local p = C.EVP_CIPHER_get0_provider(self.algo) + if p == nil then + return nil + end + return ffi_str(C.OSSL_PROVIDER_get0_name(p)) +end + +if OPENSSL_3X then + local param_lib = require "resty.openssl.param" + _M.settable_params, _M.set_params, _M.gettable_params, _M.get_param = param_lib.get_params_func("EVP_CIPHER_CTX") +end + +function _M:init(key, iv, opts) + opts = opts or {} + if not key or #key ~= self.key_size then + return false, string.format("cipher:init: incorrect key size, expect %d", self.key_size) + end + if not iv or #iv ~= self.iv_size then + return false, string.format("cipher:init: incorrect iv size, expect %d", self.iv_size) + end + + -- always passed in the `EVP_CIPHER` parameter to reinitialized the cipher + -- it will have a same effect as EVP_CIPHER_CTX_cleanup/EVP_CIPHER_CTX_reset then Init_ex with + -- empty algo + if C.EVP_CipherInit_ex(self.ctx, self.algo, nil, key, iv, opts.is_encrypt and 1 or 0) == 0 then + return false, format_error("cipher:init EVP_CipherInit_ex") + end + + if opts.no_padding then + -- EVP_CIPHER_CTX_set_padding() always returns 1. + C.EVP_CIPHER_CTX_set_padding(self.ctx, 0) + end + + self.initialized = true + + return true +end + +function _M:encrypt(key, iv, s, no_padding, aead_aad) + local _, err = self:init(key, iv, { + is_encrypt = true, + no_padding = no_padding, + }) + if err then + return nil, err + end + if aead_aad then + local _, err = self:update_aead_aad(aead_aad) + if err then + return nil, err + end + end + return self:final(s) +end + +function _M:decrypt(key, iv, s, no_padding, aead_aad, aead_tag) + local _, err = self:init(key, iv, { + is_encrypt = false, + no_padding = no_padding, + }) + if err then + return nil, err + end + if aead_aad then + local _, err = self:update_aead_aad(aead_aad) + if err then + return nil, err + end + end + if aead_tag then + local _, err = self:set_aead_tag(aead_tag) + if err then + return nil, err + end + end + return self:final(s) +end + +-- https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption +function _M:update_aead_aad(aad) + if not self.initialized then + return nil, "cipher:update_aead_aad: cipher not initalized, call cipher:init first" + end + + if C.EVP_CipherUpdate(self.ctx, nil, out_length, aad, #aad) ~= 1 then + return false, format_error("cipher:update_aead_aad") + end + return true +end + +function _M:get_aead_tag(size) + if not self.initialized then + return nil, "cipher:get_aead_tag: cipher not initalized, call cipher:init first" + end + + size = size or self.key_size / 2 + if size > self.key_size then + return nil, string.format("tag size %d is too large", size) + end + if C.EVP_CIPHER_CTX_ctrl(self.ctx, evp_macro.EVP_CTRL_AEAD_GET_TAG, size, out_buffer) ~= 1 then + return nil, format_error("cipher:get_aead_tag") + end + + return ffi_str(out_buffer, size) +end + +function _M:set_aead_tag(tag) + if not self.initialized then + return nil, "cipher:set_aead_tag: cipher not initalized, call cipher:init first" + end + + if type(tag) ~= "string" then + return false, "cipher:set_aead_tag expect a string at #1" + end + local tag_void_ptr = ffi_cast(void_ptr, tag) + if C.EVP_CIPHER_CTX_ctrl(self.ctx, evp_macro.EVP_CTRL_AEAD_SET_TAG, #tag, tag_void_ptr) ~= 1 then + return false, format_error("cipher:set_aead_tag") + end + + return true +end + +function _M:update(...) + if not self.initialized then + return nil, "cipher:update: cipher not initalized, call cipher:init first" + end + + local ret = {} + for i, s in ipairs({...}) do + local inl = #s + if inl > 1024 then + s = ffi_cast(uchar_ptr, s) + for i=0, inl-1, 1024 do + local chunk_size = 1024 + if inl - i < 1024 then + chunk_size = inl - i + end + if C.EVP_CipherUpdate(self.ctx, out_buffer, out_length, s+i, chunk_size) ~= 1 then + return nil, format_error("cipher:update") + end + table.insert(ret, ffi_str(out_buffer, out_length[0])) + end + else + if C.EVP_CipherUpdate(self.ctx, out_buffer, out_length, s, inl) ~= 1 then + return nil, format_error("cipher:update") + end + table.insert(ret, ffi_str(out_buffer, out_length[0])) + end + end + return table.concat(ret, "") +end + +function _M:final(s) + local ret, err + if s then + ret, err = self:update(s) + if err then + return nil, err + end + end + if C.EVP_CipherFinal_ex(self.ctx, out_buffer, out_length) ~= 1 then + return nil, format_error("cipher:final: EVP_CipherFinal_ex") + end + local final_ret = ffi_str(out_buffer, out_length[0]) + return ret and (ret .. final_ret) or final_ret +end + + +function _M:derive(key, salt, count, md, md_properties) + if type(key) ~= "string" then + return nil, nil, "cipher:derive: expect a string at #1" + elseif salt and type(salt) ~= "string" then + return nil, nil, "cipher:derive: expect a string at #2" + elseif count then + count = tonumber(count) + if not count then + return nil, nil, "cipher:derive: expect a number at #3" + end + elseif md and type(md) ~= "string" then + return nil, nil, "cipher:derive: expect a string or nil at #4" + end + + if salt then + if #salt > 8 then + ngx.log(ngx.WARN, "cipher:derive: salt is too long, truncate salt to 8 bytes") + salt = salt:sub(0, 8) + elseif #salt < 8 then + ngx.log(ngx.WARN, "cipher:derive: salt is too short, padding with zero bytes to length") + salt = salt .. string.rep('\000', 8 - #salt) + end + end + + local mdt + if OPENSSL_3X then + mdt = C.EVP_MD_fetch(ctx_lib.get_libctx(), md or 'sha1', md_properties) + else + mdt = C.EVP_get_digestbyname(md or 'sha1') + end + if mdt == nil then + return nil, nil, string.format("cipher:derive: invalid digest type \"%s\"", md) + end + local cipt = C.EVP_CIPHER_CTX_cipher(self.ctx) + local keyb = uchar_array(self.key_size) + local ivb = uchar_array(self.iv_size) + + local size = C.EVP_BytesToKey(cipt, mdt, salt, + key, #key, count or 1, + keyb, ivb) + if size == 0 then + return nil, nil, format_error("cipher:derive: EVP_BytesToKey") + end + + return ffi_str(keyb, size), ffi_str(ivb, self.iv_size) +end + +return _M |