diff options
author | Vsevolod Stakhov <vsevolod@rspamd.com> | 2024-09-20 19:00:05 +0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-20 19:00:05 +0600 |
commit | f8fdd2ba0881fdfbbe371e114781a6fc87809138 (patch) | |
tree | f6bfd77ec8c1c1503b2ce7a7201ad5d9b9877985 | |
parent | f32ee02772bad947ebaa4818940591a28c079e7c (diff) | |
parent | 92b679d17ca41f85009c9e33cdd5967f955b5557 (diff) | |
download | rspamd-f8fdd2ba0881fdfbbe371e114781a6fc87809138.tar.gz rspamd-f8fdd2ba0881fdfbbe371e114781a6fc87809138.zip |
Merge branch 'master' into vstakhov-cpu-detectionvstakhov-cpu-detection
-rw-r--r-- | lualib/lua_util.lua | 11 | ||||
-rw-r--r-- | lualib/rspamadm/secretbox.lua | 157 | ||||
-rw-r--r-- | src/plugins/lua/dmarc.lua | 12 | ||||
-rw-r--r-- | test/functional/cases/150_rspamadm.robot | 47 | ||||
-rw-r--r-- | test/lua/unit/lua_util.maybe_encrypt_decrypt_header.lua | 11 | ||||
-rw-r--r-- | utils/encrypt_decrypt_header.py | 97 |
6 files changed, 326 insertions, 9 deletions
diff --git a/lualib/lua_util.lua b/lualib/lua_util.lua index ffc07842e..62b38c87e 100644 --- a/lualib/lua_util.lua +++ b/lualib/lua_util.lua @@ -1305,12 +1305,12 @@ 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) + logger.errx(rspamd_config, "Header is empty or nil. Header: %s", 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) + logger.errx(rspamd_config, "Key is empty or nil. Key: %s", key) return header end local cryptobox = rspamd_secretbox.create(key) @@ -1322,7 +1322,8 @@ exports.maybe_encrypt_header = function(header, settings, prefix) else encrypted_header = cryptobox:encrypt(header, nonce) end - return encrypted_header, nonce + encrypted_header = nonce .. encrypted_header + return encrypted_header end end @@ -1342,12 +1343,12 @@ exports.maybe_decrypt_header = function(encrypted_header, settings, prefix, nonc 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") + logger.errx(rspamd_config, "Encrypted header is empty or nil. Encrypted header: %s", encrypted_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") + logger.errx(rspamd_config, "Key is empty or nil. Key: %s", key) return encrypted_header end local cryptobox = rspamd_secretbox.create(key) diff --git a/lualib/rspamadm/secretbox.lua b/lualib/rspamadm/secretbox.lua new file mode 100644 index 000000000..3ab10cee0 --- /dev/null +++ b/lualib/rspamadm/secretbox.lua @@ -0,0 +1,157 @@ +local util = require 'lua_util' +local rspamd_util = require 'rspamd_util' +local argparse = require 'argparse' + +local parser = argparse() + :name "secretbox" + :description "Encrypt/decrypt given text with given key and nonce" + :help_description_margin(32) + :command_target('command') + :require_command(true) + +parser:mutex( + parser:flag '-R --raw' + :description('Encrypted text(and nonce if it is there) will be given in raw'), + parser:flag '-H --hex' + :description('Encrypted text(and nonce if it is there) will be given in hex'), + parser:flag '-b --base32' + :description('Encrypted text(and nonce if it is there) will be given in base32'), + parser:flag '-B --base64' + :description('Encrypted text(and nonce if it is there) will be given in base64') +) + +local decrypt = parser:command 'decrypt' + :description 'Decrypt text with given key and nonce' + +decrypt:option "-t --text" + :description("Encrypted text(Base 64)") + :argname("<text>") +decrypt:option "-k --key" + :description("Key used to encrypt text") + :argname("<key>") +decrypt:option "-n --nonce" + :description("Nonce used to encrypt text(Base 64)") + :argname("<nonce>") + :default(nil) + +local encrypt = parser:command 'encrypt' + :description 'Encrypt text with given key' + +encrypt:option "-t --text" + :description("Text to encrypt") + :argname("<text>") +encrypt:option "-k --key" + :description("Key to encrypt text") + :argname("<key>") +encrypt:option "-n --nonce" + :description("Nonce to encrypt text(Base 64)") + :argname("<nonce>") + :default(nil) + +local function set_up_encoding(args, type, text) + local function fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) + end + + local function tohex(str) + return (str:gsub('.', function (c) + return string.format('%02X', string.byte(c)) + end)) + end + + local output = text + + if type == 'encode' then + if args.hex then + output = tohex(text) + elseif args.base32 then + output = rspamd_util.encode_base32(text) + elseif args.base64 then + output = rspamd_util.encode_base64(text) + end + elseif type == 'decode' then + if args.hex then + output = fromhex(text) + elseif args.base32 then + output = rspamd_util.decode_base32(text) + elseif args.base64 then + output = rspamd_util.decode_base64(text) + end + end + + return output +end + +local function decryption_handler(args) + local settings = { + prefix = 'dec', + dec_encrypt = true, + dec_key = args.key + } + + local decrypted_header = '' + if(args.nonce ~= nil) then + local decoded_text = set_up_encoding(args, 'decode', tostring(args.text)) + local decoded_nonce = set_up_encoding(args, 'decode', tostring(args.nonce)) + + decrypted_header = util.maybe_decrypt_header(decoded_text, settings, settings.prefix, decoded_nonce) + else + local text_with_nonce = set_up_encoding(args, 'decode', tostring(args.text)) + local nonce = string.sub(tostring(text_with_nonce), 1, 24) + local text = string.sub(tostring(text_with_nonce), 25) + + decrypted_header = util.maybe_decrypt_header(text, settings, settings.prefix, nonce) + end + + if decrypted_header ~= nil then + print(decrypted_header) + else + print('The decryption failed. Please check the correctness of the arguments given.') + end +end + +local function encryption_handler(args) + local settings = { + prefix = 'dec', + dec_encrypt = true, + dec_key = args.key, + } + + if args.nonce ~= nil then + settings.dec_nonce = set_up_encoding(args, 'decode', tostring(args.nonce)) + end + + local encrypted_text = util.maybe_encrypt_header(args.text, settings, settings.prefix) + + if encrypted_text ~= nil then + print(set_up_encoding(args, 'encode', tostring(encrypted_text))) + else + print('The encryption failed. Please check the correctness of the arguments given.') + end +end + +local command_handlers = { + decrypt = decryption_handler, + encrypt = encryption_handler, +} + +local function handler(args) + local cmd_opts = parser:parse(args) + + local f = command_handlers[cmd_opts.command] + if not f then + parser:error(string.format("command isn't implemented: %s", + cmd_opts.command)) + end + f(cmd_opts) +end + + +return { + name = 'secret_box', + aliases = { 'secretbox', 'secret_box' }, + handler = handler, + description = parser._description +}
\ No newline at end of file diff --git a/src/plugins/lua/dmarc.lua b/src/plugins/lua/dmarc.lua index 792672bd0..28b3db867 100644 --- a/src/plugins/lua/dmarc.lua +++ b/src/plugins/lua/dmarc.lua @@ -264,6 +264,13 @@ local function dmarc_validate_policy(task, policy, hdrfromdom, dmarc_esld) end if policy.rua and redis_params and settings.reporting.enabled then + if settings.reporting.only_domains then + if not (settings.reporting.only_domains:get_key(policy.domain) or + settings.reporting.only_domains:get_key(rspamd_util.get_tld(policy.domain))) then + rspamd_logger.info(task, 'DMARC reporting suppressed for sender domain %s', policy.domain) + return + end + end if settings.reporting.exclude_domains then if settings.reporting.exclude_domains:get_key(policy.domain) or settings.reporting.exclude_domains:get_key(rspamd_util.get_tld(policy.domain)) then @@ -527,6 +534,11 @@ if type(settings.reporting) == 'table' then type = 'map', description = 'Recipients not to store DMARC reports for' }, + only_domains = { + optional = true, + type = 'map', + description = 'Domains to store DMARC reports about' + }, }) end diff --git a/test/functional/cases/150_rspamadm.robot b/test/functional/cases/150_rspamadm.robot index 6bff14b2e..257b0b501 100644 --- a/test/functional/cases/150_rspamadm.robot +++ b/test/functional/cases/150_rspamadm.robot @@ -4,6 +4,13 @@ Suite Teardown Rspamadm Teardown Library ${RSPAMD_TESTDIR}/lib/rspamd.py Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot +*** Variables *** +${TEXT} text +${KEY} 12345678901234567890123456789012 +${NONCE} 9pyeEd986hrjcpozCIZ41jEo6dCDbgjg +${ENCRYPTED_TEXT} 8KGF6VLI7vnweUdR8FuQZuT+ID8= +${PYTHON_SCRIPT} ${RSPAMD_TESTDIR}/../../utils/encrypt_decrypt_header.py + *** Test Cases *** Config Test ${result} = Rspamadm configtest @@ -46,3 +53,43 @@ Verbose mode Should Match Regexp ${result.stderr} ^$ Should Match Regexp ${result.stdout} hello world\n Should Be Equal As Integers ${result.rc} 0 + +SecretBox rspamadm encrypt/decrypt + ${result} = Rspamadm secret_box -B encrypt -t ${TEXT} -k ${KEY} -n ${NONCE} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${result.stdout} ${NONCE}${ENCRYPTED_TEXT} + ${result1} = Rspamadm secret_box -B decrypt -t ${ENCRYPTED_TEXT} -k ${KEY} -n ${NONCE} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${result1.stdout} ${TEXT} + +SecretBox python encrypt/decrypt + ${result} = Run Process python3 ${PYTHON_SCRIPT} -B encrypt -t ${TEXT} -k ${KEY} -n ${NONCE} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${result.stdout} ${NONCE}${ENCRYPTED_TEXT} + ${result1} = Run Process python3 ${PYTHON_SCRIPT} -B decrypt -t ${NONCE}${ENCRYPTED_TEXT} -k ${KEY} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${result1.stdout} ${TEXT} + +SecretBox encrypt python with nonce decrypt rspamadm + ${result} = Run Process python3 ${PYTHON_SCRIPT} -B encrypt -t ${TEXT} -k ${KEY} -n ${NONCE} + ${result1} = Rspamadm secret_box -B decrypt -t ${result.stdout} -k ${KEY} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${TEXT} ${result1.stdout} + +SecretBox encrypt python without nonce decrypt rspamadm + ${result} = Run Process python3 ${PYTHON_SCRIPT} -B encrypt -t ${TEXT} -k ${KEY} + ${result1} = Rspamadm secret_box -B decrypt -t ${result.stdout} -k ${KEY} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${TEXT} ${result1.stdout} + +SecretBox encrypt rspamadm with nonce decrypt python + ${result} = Rspamadm secret_box -B encrypt -t ${TEXT} -k ${KEY} -n ${NONCE} + ${result1} = Run Process python3 ${PYTHON_SCRIPT} -B decrypt -t ${result.stdout} -k ${KEY} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${TEXT} ${result1.stdout} + +SecretBox encrypt rspamadm without nonce decrypt python + ${result} = Rspamadm secret_box -B encrypt -t ${TEXT} -k ${KEY} + ${result1} = Run Process python3 ${PYTHON_SCRIPT} -B decrypt -t ${result.stdout} -k ${KEY} + Should Match Regexp ${result.stderr} ^$ + Should Be Equal As Strings ${TEXT} ${result1.stdout} diff --git a/test/lua/unit/lua_util.maybe_encrypt_decrypt_header.lua b/test/lua/unit/lua_util.maybe_encrypt_decrypt_header.lua index 613101068..ef31f5e9b 100644 --- a/test/lua/unit/lua_util.maybe_encrypt_decrypt_header.lua +++ b/test/lua/unit/lua_util.maybe_encrypt_decrypt_header.lua @@ -15,7 +15,8 @@ context("Lua util - maybe encrypt/decrypt header", function() assert_true(false, 'Failed to encrypt header') end - local decrypted_header = util.maybe_decrypt_header(encrypted_header, settings, settings.prefix) + local text = string.sub(tostring(encrypted_header), 6) + local decrypted_header = util.maybe_decrypt_header(text, settings, settings.prefix) if decrypted_header == encrypted_header or decrypted_header == nil then assert_true(false, 'Failed to decrypt header') end @@ -36,13 +37,15 @@ context("Lua util - maybe encrypt/decrypt header", function() prefix_key = 'key' } - local encrypted_header, nonce = util.maybe_encrypt_header(header, settings, settings.prefix) + 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, nonce) + local nonce = string.sub(tostring(encrypted_header), 1, 24) + local text = string.sub(tostring(encrypted_header), 25) + local decrypted_header = util.maybe_decrypt_header(text, settings, settings.prefix, nonce) + if decrypted_header == encrypted_header or decrypted_header == nil then assert_true(false, 'Failed to decrypt header') end diff --git a/utils/encrypt_decrypt_header.py b/utils/encrypt_decrypt_header.py new file mode 100644 index 000000000..5f2ea755e --- /dev/null +++ b/utils/encrypt_decrypt_header.py @@ -0,0 +1,97 @@ +import argparse +import base64 + +import nacl.encoding +from nacl.secret import SecretBox +from nacl.hash import blake2b + + +def create_secret_box(key): + key = blake2b(key, encoder=nacl.encoding.RawEncoder) + box = SecretBox(key) + return box + +def encrypt_text(header, key, nonce): + box = create_secret_box(key) + if nonce is not None: + encrypted_header = box.encrypt(header, nonce=nonce) + else: + encrypted_header = box.encrypt(header) + return encrypted_header + +def decrypt_text(encrypted_header, key): + box = create_secret_box(key) + decrypted_header = box.decrypt(encrypted_header) + return decrypted_header + +def set_encoding(args, type_, text): + output = text + if type_ == 'encode': + if args.hex: + output = base64.b16encode(text) + elif args.base32: + output = base64.b32encode(text) + elif args.base64: + output = base64.b64encode(text) + elif type_ == 'decode': + if args.hex: + output = base64.b16decode(text) + elif args.base32: + output = base64.b32decode(text) + elif args.base64: + output = base64.b64decode(text) + return output + +def set_up_parser_args(): + new_parser = argparse.ArgumentParser(description="Encrypt or Decrypt a text.") + enc_group = new_parser.add_mutually_exclusive_group() + + enc_group.add_argument("-r", "--raw", action="store_true", + help="Encrypted text(and nonce if it is there) will be given in raw") + enc_group.add_argument("-H", "--hex", action="store_true", + help="Encrypted text(and nonce if it is there) will be given in hex") + enc_group.add_argument("-b", "--base32", action="store_true", + help="Encrypted text(and nonce if it is there) will be given in base32") + enc_group.add_argument("-B", "--base64", action="store_true", + help="Encrypted text(and nonce if it is there) will be given in base64") + + subparsers = new_parser.add_subparsers(dest="command", help="encrypt or decrypt") + + encrypt_parser = subparsers.add_parser("encrypt", help="Encrypt a text") + encrypt_parser.add_argument("-t", "--text", type=str, required=True, help="Text to encrypt") + encrypt_parser.add_argument("-k", "--key", type=str, required=True, help="Encryption key") + encrypt_parser.add_argument("-n", "--nonce", type=str, required=False, help="Encryption nonce") + + decrypt_parser = subparsers.add_parser("decrypt", help="Decrypt a text") + decrypt_parser.add_argument("-t", "--encrypted_text", type=str, required=True, help="Encrypted text") + decrypt_parser.add_argument("-k", "--key", type=str, required=True, help="Decryption key") + + args = new_parser.parse_args() + return args + +def main(): + args = set_up_parser_args() + + if args.command == "encrypt": + text = args.text.encode() + key = args.key.encode() + if args.nonce is not None: + nonce = set_encoding(args, 'decode', args.nonce) + else: + nonce = None + + encrypted_text = encrypt_text(text, key, nonce) + if args.raw: + print(set_encoding(args, 'encode', encrypted_text)) + else: + print(set_encoding(args, 'encode', encrypted_text).decode()) + + elif args.command == "decrypt": + encrypted_text = set_encoding(args, 'decode', args.encrypted_text) + key = args.key.encode() + + decrypted_text = decrypt_text(encrypted_text, key) + print(decrypted_text.decode()) + +if __name__ == "__main__": + main()
\ No newline at end of file |