]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] ARC module now supports ed25519 keys
authorVsevolod Stakhov <vsevolod@rspamd.com>
Wed, 15 Oct 2025 11:39:54 +0000 (12:39 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Wed, 15 Oct 2025 11:39:54 +0000 (12:39 +0100)
- 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

src/plugins/lua/arc.lua

index 30a61326f03b14811e42c3cb96814fa8568c93f6..b681572adab7ea89d27ee2b0cba75d402c0c14d4 100644 (file)
@@ -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