return subject
end
+---[[[
+-- @function lua_util.maybe_encrypt_header(header, settings, prefix)
+-- Encode header with configured public key if enabled in settings.
+-- If header is not set then nil is returned. If pub_key is empty then header is returned.
+-- Supported settings:
+-- * <prefix>_encrypt = false - no need for encryption of a header
+-- * <prefix>_key = 'key' - key that is used encrypt header
+-- * <prefix>_nonce = 'nonce' - nonce to encrypt header(optional)
+-- @return encrypted header
+---]]]
+exports.maybe_encrypt_header = function(header, settings, prefix)
+ local rspamd_secretbox = require "rspamd_cryptobox_secretbox"
+
+ if not header or header == '' then
+ logger.errx(rspamd_config, "Header: %s is empty or nil", header)
+ return nil
+ elseif settings[prefix .. '_encrypt'] then
+ local key = settings[prefix .. '_key']
+ if not key or key == '' then
+ logger.errx(rspamd_config, "Key: %s is empty or nil", key)
+ return header
+ end
+ local cryptobox = rspamd_secretbox.create(key)
+
+ local nonce = settings[prefix .. '_nonce']
+ local encrypted_header = ''
+ if not nonce or nonce == '' then
+ encrypted_header, nonce = cryptobox:encrypt(header)
+ else
+ encrypted_header = cryptobox:encrypt(header, nonce)
+ end
+ return encrypted_header, nonce
+ end
+end
+
+---[[[
+-- @function lua_util.maybe_decrypt_header(header, settings, prefix, nonce)
+-- Decode enoced with configured public_key header if enabled in settings.
+-- If encoded header is not set then nil is returned. If pub_key is empty then encoded header is returned.
+-- Supported settings:
+-- * <prefix>_encrypt = false - no need for decryption of a header
+-- * <prefix>_key = 'key' - key that is used decrypt header
+-- * <prefix>_nonce = 'nonce' - nonce used to encrypt header(optional)
+-- Nonce is an optional argument if <prefix>_nonce is provided, otherwise it is an required argument
+-- and <prefix>_nonce is an optional
+-- @return decrypted header
+---]]]
+exports.maybe_decrypt_header = function(encrypted_header, settings, prefix, nonce)
+ local rspamd_secretbox = require "rspamd_cryptobox_secretbox"
+
+ if not encrypted_header or encrypted_header == '' then
+ logger.errx(rspamd_config, "Encoded header: %s is empty or nil")
+ return nil
+ elseif settings[prefix .. '_encrypt'] then
+ local key = settings[prefix .. '_key']
+ if not key or key == '' then
+ logger.errx(rspamd_config, "Key: %s is empty or nil")
+ return encrypted_header
+ end
+ local cryptobox = rspamd_secretbox.create(key)
+
+ local result = false
+ local header = ''
+ if not nonce then
+ result, header = cryptobox:decrypt(encrypted_header, settings[prefix .. '_nonce'])
+ else
+ result, header = cryptobox:decrypt(encrypted_header, nonce)
+ end
+
+ if not result then
+ logger.infox(rspamd_config, "Decryption is failed with result: %s and decrypted header: %s", result, header)
+ return nil
+ end
+
+ return header
+ end
+end
+
---[[[
-- @function lua_util.callback_from_string(str)
-- Converts a string like `return function(...) end` to lua function and return true and this function
#include <stdalign.h>
#include <openssl/hmac.h>
-
+#if OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/provider.h>
+#endif
enum lua_cryptobox_hash_type {
LUA_CRYPTOBOX_HASH_BLAKE2 = 0,
union {
rspamd_cryptobox_hash_state_t *h;
EVP_MD_CTX *c;
+#if OPENSSL_VERSION_MAJOR >= 3
+ EVP_MAC_CTX *hmac_c;
+#else
HMAC_CTX *hmac_c;
+#endif
rspamd_cryptobox_fast_hash_state_t *fh;
} content;
EVP_DigestUpdate(h->content.c, p, len);
break;
case LUA_CRYPTOBOX_HASH_HMAC:
+#if OPENSSL_VERSION_MAJOR >= 3
+ EVP_MAC_update(h->content.hmac_c, p, len);
+#else
HMAC_Update(h->content.hmac_c, p, len);
+#endif
break;
case LUA_CRYPTOBOX_HASH_XXHASH64:
case LUA_CRYPTOBOX_HASH_XXHASH32:
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x30500000)
HMAC_CTX_cleanup(h->content.hmac_c);
g_free(h->content.hmac_c);
+#else
+#if OPENSSL_VERSION_MAJOR >= 3
+ EVP_MAC_CTX_free(h->content.hmac_c);
#else
HMAC_CTX_free(h->content.hmac_c);
+#endif
#endif
}
else if (h->type == LUA_CRYPTOBOX_HASH_BLAKE2) {
bool insecure)
{
h->type = LUA_CRYPTOBOX_HASH_HMAC;
+ OSSL_PROVIDER *dflt = OSSL_PROVIDER_load(NULL, "default");
+
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+ if (insecure) {
+ /* Should never ever be used for crypto/security purposes! */
+#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW
+#if OPENSSL_VERSION_MAJOR >= 3
+ OSSL_PROVIDER *fips = OSSL_PROVIDER_load(NULL, "fips");
+#endif
+ }
+#endif
+#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x30500000)
h->content.hmac_c = g_malloc0(sizeof(*h->content.hmac_c));
+#else
+#if OPENSSL_VERSION_MAJOR >= 3
+ EVP_MAC* mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+ h->content.hmac_c = EVP_MAC_CTX_new(mac);
+ EVP_MAC_free(mac);
#else
h->content.hmac_c = HMAC_CTX_new();
#endif
- h->out_len = EVP_MD_size(htype);
+#endif
#if OPENSSL_VERSION_NUMBER > 0x10100000L
if (insecure) {
/* Should never ever be used for crypto/security purposes! */
#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW
+#if OPENSSL_VERSION_MAJOR < 3
HMAC_CTX_set_flags(h->content.hmac_c, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+#endif
#endif
}
#endif
+ h->out_len = EVP_MD_size(htype);
+#if OPENSSL_VERSION_MAJOR >= 3
+ OSSL_PARAM params[2];
+ params[0] = OSSL_PARAM_construct_utf8_string("digest", EVP_MD_get0_name(htype), 0);
+ params[1] = OSSL_PARAM_construct_end();
+
+ EVP_MAC_init(h->content.hmac_c, key, keylen, params);
+#else
HMAC_Init_ex(h->content.hmac_c, key, keylen, htype, NULL);
+#endif
}
static struct rspamd_lua_cryptobox_hash *
rspamd_cryptobox_hash_init(h->content.h, NULL, 0);
break;
case LUA_CRYPTOBOX_HASH_SSL:
- EVP_DigestInit(h->content.c, EVP_MD_CTX_md(h->content.c));
+ EVP_DigestInit(h->content.c, EVP_MD_CTX_get0_md(h->content.c));
break;
case LUA_CRYPTOBOX_HASH_HMAC:
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x30500000)
/* Old openssl is awesome... */
HMAC_Init_ex(h->content.hmac_c, NULL, 0, h->content.hmac_c->md, NULL);
+#else
+#if OPENSSL_VERSION_MAJOR >= 3
+ EVP_MAC_CTX_free(h->content.hmac_c);
+ EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+ h->content.hmac_c = EVP_MAC_CTX_new(mac);
+ EVP_MAC_free(mac);
#else
HMAC_CTX_reset(h->content.hmac_c);
+#endif
#endif
break;
case LUA_CRYPTOBOX_HASH_XXHASH64:
memcpy(h->out, out, ssl_outlen);
break;
case LUA_CRYPTOBOX_HASH_HMAC:
+#if OPENSSL_VERSION_MAJOR >= 3
+ size_t ssl_outlen_size_t = ssl_outlen;
+ EVP_MAC_final(h->content.hmac_c, out, &ssl_outlen_size_t, sizeof(out));
+ ssl_outlen = ssl_outlen_size_t;
+#else
HMAC_Final(h->content.hmac_c, out, &ssl_outlen);
+#endif
h->out_len = ssl_outlen;
g_assert(ssl_outlen <= sizeof(h->out));
memcpy(h->out, out, ssl_outlen);
EVP_PKEY *pk;
e = BN_new();
- r = RSA_new();
pk = EVP_PKEY_new();
if (BN_set_word(e, RSA_F4) != 1) {
BN_free(e);
- RSA_free(r);
EVP_PKEY_free(pk);
return luaL_error(L, "BN_set_word failed");
}
- if (RSA_generate_key_ex(r, nbits, e, NULL) != 1) {
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (EVP_PKEY_keygen_init(pctx) != 1) {
BN_free(e);
- RSA_free(r);
EVP_PKEY_free(pk);
+ EVP_PKEY_CTX_free(pctx);
- return luaL_error(L, "RSA_generate_key_ex failed");
+ return luaL_error(L, "EVP_PKEY_keygen_init failed");
}
-
- if (EVP_PKEY_set1_RSA(pk, r) != 1) {
+ EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, nbits);
+ EVP_PKEY_CTX_set1_rsa_keygen_pubexp(pctx, e);
+ if (EVP_PKEY_keygen(pctx, &pk) != 1) {
BN_free(e);
- RSA_free(r);
EVP_PKEY_free(pk);
+ EVP_PKEY_CTX_free(pctx);
- return luaL_error(L, "EVP_PKEY_set1_RSA failed");
+ return luaL_error(L, "EVP_PKEY_keygen failed");
}
BIO *mbio;
mbio = BIO_new(BIO_s_mem());
/* Process private key */
- rc = i2d_RSAPrivateKey_bio(mbio, r);
+ rc = i2d_PrivateKey_bio(mbio, pk);
if (rc == 0) {
BIO_free(mbio);
BN_free(e);
- RSA_free(r);
EVP_PKEY_free(pk);
return luaL_error(L, "i2d_RSAPrivateKey_bio failed");
/* Process public key */
BIO_reset(mbio);
- rc = i2d_RSA_PUBKEY_bio(mbio, r);
+ rc = i2d_PUBKEY_bio(mbio, pk);
if (rc == 0) {
BIO_free(mbio);
BN_free(e);
- RSA_free(r);
EVP_PKEY_free(pk);
return luaL_error(L, "i2d_RSA_PUBKEY_bio failed");
pub_out->flags = RSPAMD_TEXT_FLAG_OWN;
BN_free(e);
- RSA_free(r);
EVP_PKEY_free(pk);
BIO_free(mbio);
}
struct rspamd_lua_text *out;
if (sbox == NULL) {
- return luaL_error(L, "invalid arguments");
+ return luaL_error(L, "invalid argument for secretbox state");
}
if (lua_isstring(L, 2)) {
struct rspamd_lua_text *t = lua_check_text(L, 2);
if (!t) {
- return luaL_error(L, "invalid arguments; userdata is not text");
+ return luaL_error(L, "invalid first argument; userdata is not text");
}
in = t->start;
inlen = t->len;
}
else {
- return luaL_error(L, "invalid arguments; userdata or string are expected");
+ return luaL_error(L, "invalid first argument; userdata or string are expected");
}
/* Nonce part */
struct rspamd_lua_text *t = lua_check_text(L, 3);
if (!t) {
- return luaL_error(L, "invalid arguments; userdata is not text");
+ return luaL_error(L, "invalid second argument; userdata is not text");
}
nonce = t->start;
nlen = t->len;
}
else {
- return luaL_error(L, "invalid arguments; userdata or string are expected");
+ return luaL_error(L, "invalid second argument; userdata or string are expected");
}
if (nlen < 1 || nlen > crypto_secretbox_NONCEBYTES) {
/***
* @method rspamd_cryptobox_secretbox:decrypt(input, nonce)
* Decrypts data using secretbox
- * @param {string/text} nonce nonce used to encrypt
* @param {string/text} input input to decrypt
+ * @param {string/text} nonce nonce used to encrypt
* @param {table} params optional parameters - NYI
* @return {boolean},{rspamd_text} decryption result + decrypted text
*/
struct rspamd_lua_text *out;
if (sbox == NULL) {
- return luaL_error(L, "invalid arguments");
+ return luaL_error(L, "invalid argument for secretbox state");
}
/* Input argument */
struct rspamd_lua_text *t = lua_check_text(L, 2);
if (!t) {
- return luaL_error(L, "invalid arguments; userdata is not text");
+ return luaL_error(L, "invalid first argument; userdata is not text");
}
in = t->start;
inlen = t->len;
}
else {
- return luaL_error(L, "invalid arguments; userdata or string are expected");
+ return luaL_error(L, "invalid first argument; userdata or string are expected");
}
/* Nonce argument */
struct rspamd_lua_text *t = lua_check_text(L, 3);
if (!t) {
- return luaL_error(L, "invalid arguments; userdata is not text");
+ return luaL_error(L, "invalid second argument; userdata is not text");
}
nonce = t->start;
nlen = t->len;
}
else {
- return luaL_error(L, "invalid arguments; userdata or string are expected");
+ return luaL_error(L, "invalid second argument; userdata or string are expected");
}
--- /dev/null
+local util = require 'lua_util'
+
+context("Lua util - maybe encrypt/decrypt header", function()
+ test("Encrypt/Decrypt header with nonce", function()
+ local header = tostring('X-Spamd-Result')
+ local settings = {
+ prefix = 'prefix',
+ prefix_encrypt = true,
+ prefix_key = 'key',
+ prefix_nonce = 'nonce'
+ }
+
+ local encrypted_header = util.maybe_encrypt_header(header, settings, settings.prefix)
+ if encrypted_header == header or encrypted_header == nil then
+ assert_true(false, 'Failed to encrypt header')
+ end
+
+ local decrypted_header = util.maybe_decrypt_header(encrypted_header, settings, settings.prefix)
+ if decrypted_header == encrypted_header or decrypted_header == nil then
+ assert_true(false, 'Failed to decrypt header')
+ end
+
+ if tostring(header) == tostring(decrypted_header) then
+ assert_true(true, 'Succeed to confirm equality of original header and decrypted header')
+ else
+ assert_rspamd_table_eq_sorted({actual = { decrypted_header },
+ expect = { header }})
+ end
+ end)
+
+ test("Encrypt/Decrypt header without nonce", function()
+ local header = tostring('X-Spamd-Result')
+ local settings = {
+ prefix = 'prefix',
+ prefix_encrypt = true,
+ prefix_key = 'key'
+ }
+
+ local encrypted_header, nonce = util.maybe_encrypt_header(header, settings, settings.prefix)
+ if encrypted_header == header or encrypted_header == nil then
+ assert_true(false, 'Failed to encrypt header')
+ end
+
+ local decrypted_header = util.maybe_decrypt_header(encrypted_header, settings,
+ settings.prefix, nonce)
+ if decrypted_header == encrypted_header or decrypted_header == nil then
+ assert_true(false, 'Failed to decrypt header')
+ end
+
+ if tostring(header) == tostring(decrypted_header) then
+ assert_true(true, 'Succeed to confirm equality of original header and decrypted header')
+ else
+ assert_rspamd_table_eq_sorted({actual = { decrypted_header },
+ expect = { header }})
+ end
+ end)
+end)
\ No newline at end of file