Skip to main content

openarc – Open Source ARC

Last updated November 2024

ARC is an acronym for Authenticated Received Chain. It’s a technology protocol defined in RFC8617. It provides an authenticated "chain of custody" for a message, allowing each entity that handles the message to see what entities handled it before and what the message's authentication assessment was at each step in the handling flow.

The openarc module adds ARC capability to Momentum. It provides Lua and C APIs for ARC validation on a received email, and ARC siging and sealing on an outgoing email. When the module is enabled, ARC validation and signing/sealing can be achieved through calling these APIs from hook policies.

Configuration

You need to enable the openarc module in the ecelerity configuration file to use the feature:

openarc {}

The only configuration option available to the openarc module is debug_level.

Lua APIs and examples

C APIs

All the related C structures and C API functions are defined in the header file validate/ec_openarc.h. Please refer to the header file for the usage of the C structures and functions. Please contact support if further assistance is needed.

Hook points to invoke the APIs

Invoke msys.validate.openarc.verify to do ARC verification on a received message. It would verify the existing ARC headers, including the AS (ARC Seal) and the AMS (ARC Message Signature). ARC verification shall be done before any potential message modifications, in validate_data_spool or validate_data_spool_each_rcpt hook.

Invoke msys.validate.openarc.sign to do ARC signing/sealing to add the ARC headers. It should happen after all possible message modifications are done, in the regular final_validation hook, or the new post_final_validation hook. Any message modification after msys.validate.openarc.sign is called can break the integrity of the ARC headers added during ARC signing.

Since ARC signing requires the ARC chain verification result (aka cv). msys.validate.openarc.sign will get the cv value from the ec_message context variable arc_cv for signing. If arc_cv is not set, indicating a not-yet-done ARC verification, msys.validate.openarc.sign will do ARC verification implicitly.

If message modification is expected, an MTA shall call msys.validate.openarc.verify first (to get arc_cv set) and then msys.validate.openarc.sign later in a different hooks, as recommended above.

If there is absolutely no message modification, e.g. for a passthrough MTA which doesn't alter the message other than ARC signing, calling msys.validate.openarc.sign alone can have some performance benefits: the message will be scanned once for both ARC verification and signing.

Example 1:

The following policies define an intermediate MTA which does ARC signing and can potentially modify the email, e.g. through DKIM signing, engagement tracking insertion and modification, etc. In such case, ARC verify needs to happen first, followed by the logic to alter the message (e.g. DKIM signing in the example), and then finally ARC sign at the last.

require("msys.core");
require("msys.extended.message");
require("msys.validate.openarc");
local mod = {};

function mod:validate_data_spool(msg, ac, vctx)
  -- do ARC verify
  msys.validate.openarc.verify(msg)
  local cv = msg:context_get(msys.core.ECMESS_CTX_MESS, "arc_cv")
  if cv then
    print("ARC validation result: ", cv)
  else
    print("Failed to do ARC validation. Check paniclog for reasons.")
  end
end

function mod:core_final_validation(msg, accept, vctx)
  -- do DKIM signing
  local base_domain = 'ectest.example.com';
  local header_canon = 'relaxed';
  local body_canon = 'relaxed';
  local digest = 'rsa-sha1';
  local identity = '\@ectest.example.com';
  local selector = 'dkim-s1024';
  local key_file = '/opt/msys/ecelerity/etc/conf/default/dk/ectest.example.com/dkim-s1024.key';
  local body_length_limit = 0;

  local options = {};
  options["base_domain"] = base_domain
  options["header_canon"] = header_canon
  options["body_canon"] = body_canon
  options["digest"] = digest
  options["selector"] = selector
  options["keyfile"] = key_file
  options["identity"] = identity

  msys.validate.opendkim.sign(msg, vctx, options);
end

function mod:core_post_final_validation(msg, accept, vctx)
  -- do ARC sign
  local sealer = {}
  sealer.signing_domain = "sparkpost.com"
  sealer.selector = "dkim-s1024"
  sealer.keyfile = "path-to-keyfile"
  sealer.headerlist = "From:Subject:Date:To:MIME-Version:Content-Type:DKIM-Signature"
  sealer.oversign_headerlist = "From:To:Subject"

  msys.validate.openarc.sign(msg, sealer)

  -- check sign/seal result
  local ok = msg:context_get(msys.core.ECMESS_CTX_MESS, "arc_seal")
  if ok == nil or ok == '' then
    print("ARC seal failed. No ARC set add! Check paniclog for reasons.")
  elseif ok == "skip" then
    print("ARC seal skipped. No ARC set add: ARC chain failed before reaching me.")
  else
    print("ARC seal ok. ARC set added!")
  end
end

msys.registerModule("arc_sign", mod);

Example 2:

The following policy defines an intermediate MTA relay which does not modify the message other than ARC signing. In such case, ARC verify and ARC sign can be done at the same time, in any hook in DATA phase which does not block the main tasks.

require("msys.core");
require("msys.validate.openarc");
local mod = {};

function mod:core_final_validation(msg, accept, vctx)
  -- do ARC signing (will trigger ARC verification implicitly)
  local sealer = {}
  sealer.signing_domain = "sparkpost.com"
  sealer.selector = "dkim-s1024"
  sealer.keyfile = "path-to-keyfile"
  sealer.headerlist = "From:Subject:Date:To:MIME-Version:Content-Type"
  sealer.oversign_headerlist = "From:To:Subject"

  msys.validate.openarc.sign(msg, sealer)

  -- check sign/seal result
  local ok = msg:context_get(msys.core.ECMESS_CTX_MESS, "arc_seal")
  if ok == nil or ok == '' then
    print("ARC seal failed. No ARC set add! Check paniclog for reasons.")
  elseif ok == "skip" then
    print("ARC seal skipped. No ARC set add: ARC chain failed before reaching me.")
  else
    print("ARC seal ok. ARC set added!")
  end
end


msys.registerModule("arc_sign", mod);
Was this page helpful?