From: Vsevolod Stakhov Date: Wed, 15 Oct 2025 11:39:54 +0000 (+0100) Subject: [Fix] ARC module now supports ed25519 keys X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=50c6dc5bbf8cd3259aa19fa92aa5a5ee45806f3e;p=thirdparty%2Frspamd.git [Fix] ARC module now supports ed25519 keys - Remove hardcoded RSA-only restriction in do_sign() - Replace manual RSA-specific key loading and signing in arc_sign_seal() - Use native C dkim_sign() function with sign_type='arc-seal' - Leverages existing C infrastructure that supports both RSA and ed25519 - Fixes 'DECODER routines::unsupported' error when loading ed25519 keys - Algorithm detection (rsa-sha256 vs ed25519-sha256) now automatic - Reduces arc_sign_seal() from ~100 lines to ~50 lines - No FFI dependency, works with plain Lua installations Resolves RSP-76 --- diff --git a/src/plugins/lua/arc.lua b/src/plugins/lua/arc.lua index 30a61326f0..b681572ada 100644 --- a/src/plugins/lua/arc.lua +++ b/src/plugins/lua/arc.lua @@ -18,11 +18,8 @@ local rspamd_logger = require "rspamd_logger" local lua_util = require "lua_util" local dkim_sign_tools = require "lua_dkim_tools" local rspamd_util = require "rspamd_util" -local rspamd_rsa_privkey = require "rspamd_rsa_privkey" -local rspamd_rsa = require "rspamd_rsa" local fun = require "fun" local lua_auth_results = require "lua_auth_results" -local hash = require "rspamd_cryptobox_hash" local lua_mime = require "lua_mime" if confighelp then @@ -523,27 +520,8 @@ rspamd_config:register_dependency('ARC_CHECK', 'SPF_CHECK') rspamd_config:register_dependency('ARC_CHECK', 'DKIM_CHECK') local function arc_sign_seal(task, params, header) - local arc_sigs = task:cache_get('arc-sigs') local arc_seals = task:cache_get('arc-seals') - local arc_auth_results = task:cache_get('arc-authres') local cur_auth_results - local privkey - - if params.rawkey then - -- Distinguish between pem and base64 - if string.match(params.rawkey, '^-----BEGIN') then - privkey = rspamd_rsa_privkey.load_pem(params.rawkey) - else - privkey = rspamd_rsa_privkey.load_base64(params.rawkey) - end - elseif params.key then - privkey = rspamd_rsa_privkey.load_file(params.key) - end - - if not privkey then - rspamd_logger.errx(task, 'cannot load private key for signing') - return - end if settings.reuse_auth_results then local ar_header = task:get_header('Authentication-Results') @@ -559,33 +537,9 @@ local function arc_sign_seal(task, params, header) cur_auth_results = lua_auth_results.gen_auth_results(task, ar_settings) or '' end - local sha_ctx = hash.create_specific('sha256') - - -- Update using previous seals + sigs + AAR local cur_idx = 1 if arc_seals then cur_idx = #arc_seals + 1 - -- We use the cached version per each ARC-* header field individually, already sorted by instance - -- value in ascending order - for i = 1, #arc_seals, 1 do - if arc_auth_results[i] then - local s = dkim_canonicalize('ARC-Authentication-Results', - arc_auth_results[i].raw_header) - sha_ctx:update(s) - lua_util.debugm(N, task, 'update signature with header: %s', s) - end - if arc_sigs[i] then - local s = dkim_canonicalize('ARC-Message-Signature', - arc_sigs[i].raw_header) - sha_ctx:update(s) - lua_util.debugm(N, task, 'update signature with header: %s', s) - end - if arc_seals[i] then - local s = dkim_canonicalize('ARC-Seal', arc_seals[i].raw_header) - sha_ctx:update(s) - lua_util.debugm(N, task, 'update signature with header: %s', s) - end - end end header = lua_util.fold_header_with_encoding(task, @@ -599,42 +553,40 @@ local function arc_sign_seal(task, params, header) cur_auth_results, { stop_chars = ';', structured = true, encode = false }) - local s = dkim_canonicalize('ARC-Authentication-Results', - cur_auth_results) - sha_ctx:update(s) - lua_util.debugm(N, task, 'update signature with header: %s', s) - s = dkim_canonicalize('ARC-Message-Signature', header) - sha_ctx:update(s) - lua_util.debugm(N, task, 'update signature with header: %s', s) - - local cur_arc_seal = string.format('i=%d; s=%s; d=%s; t=%d; a=rsa-sha256; cv=%s; b=', - cur_idx, - params.selector, - params.domain, - math.floor(rspamd_util.get_time()), params.arc_cv) - s = string.format('%s:%s', 'arc-seal', cur_arc_seal) - sha_ctx:update(s) - lua_util.debugm(N, task, 'initial update signature with header: %s', s) - - local nl_type - if task:has_flag("milter") then - nl_type = "lf" - else - nl_type = task:get_newlines_type() - end - - local sig = rspamd_rsa.sign_memory(privkey, sha_ctx:bin()) - cur_arc_seal = string.format('%s%s', cur_arc_seal, - sig:base64(70, nl_type)) - + -- Temporarily add AAR and AMS headers so they can be signed by the seal lua_mime.modify_headers(task, { add = { ['ARC-Authentication-Results'] = { order = 1, value = cur_auth_results }, ['ARC-Message-Signature'] = { order = 1, value = header }, + }, + }) + + -- Create ARC-Seal using the native C dkim_sign function + -- This supports both RSA and ed25519 keys automatically + local seal_params = { + domain = params.domain, + selector = params.selector, + key = params.key, + rawkey = params.rawkey, + sign_type = 'arc-seal', + arc_idx = cur_idx, + arc_cv = params.arc_cv, + headers = 'arc-authentication-results:arc-message-signature' .. + string.rep(':arc-seal', cur_idx - 1), -- Include previous seals + } + + local dret, seal_header = dkim_sign(task, seal_params) + if not dret then + rspamd_logger.errx(task, 'cannot create ARC seal signature') + return + end + + lua_mime.modify_headers(task, { + add = { ['ARC-Seal'] = { order = 1, value = lua_util.fold_header_with_encoding(task, - 'ARC-Seal', cur_arc_seal, + 'ARC-Seal', seal_header, { structured = true, encode = false }) } }, @@ -712,11 +664,6 @@ local function prepare_arc_selector(task, sel) end local function do_sign(task, sign_params) - if sign_params.alg and sign_params.alg ~= 'rsa' then - -- No support for ed25519 keys - return - end - if not prepare_arc_selector(task, sign_params) then -- Broken arc return