* [pmg-devel] [PATCH pmg-api v8] fix #2971: DKIM: add setting to use From header when signing
@ 2024-02-26 14:03 Maximiliano Sandoval
2024-02-26 16:34 ` [pmg-devel] applied: " Stoiko Ivanov
0 siblings, 1 reply; 2+ messages in thread
From: Maximiliano Sandoval @ 2024-02-26 14:03 UTC (permalink / raw)
To: pmg-devel
Following RFC 5322 [2], we add an option to use the address from the
`From:` header instead of the Envelope From address.
From RFC 7489 [1]:
To illustrate, in relaxed mode, if a validated DKIM signature
successfully verifies with a "d=" domain of "example.com", and the
RFC5322.From address is "alerts@news.example.com", the DKIM "d="
domain and the RFC5322.From domain are considered to be "in
alignment". In strict mode, this test would fail, since the "d="
domain does not exactly match the FQDN of the address.
Tested with the following command:
swaks --from foo@envelope.domain --to EMAIL -s PMG_ADDR:26 --data "Date: %DATE%\nTo: %TO_ADDRESS%\nFrom: bar@header.domain\nSubject: test %DATE%\nMessage-Id: <%MESSAGEID%>\nX-Mailer: swaks v%SWAKS_VERSION% jetmore.org/john/code/swaks/\n%NEW_HEADERS%\n%BODY%\n"
Where EMAIL and PMG_ADDR are given adequate values, and envelope.domain
and header.domain are in the 'Sign Domains' list.
[1] https://datatracker.ietf.org/doc/html/rfc7489#section-3.1.1
[2] https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
---
Differences from v7:
- Simplify code counting the number of senders
- `die` when the sender count is not 1
Differences from v6:
- Do not use uppercase in commit title
- Rename dkim_use_domain to dkim-use-domain
src/PMG/Config.pm | 8 +++++++
src/PMG/DKIMSign.pm | 48 ++++++++++++++++++++++++++++++++++------
src/PMG/RuleDB/Accept.pm | 3 +--
src/PMG/RuleDB/BCC.pm | 3 +--
src/bin/pmg-smtp-filter | 1 +
5 files changed, 52 insertions(+), 11 deletions(-)
diff --git a/src/PMG/Config.pm b/src/PMG/Config.pm
index 71d4c0b..0c5419e 100644
--- a/src/PMG/Config.pm
+++ b/src/PMG/Config.pm
@@ -134,6 +134,12 @@ EODESC
description => "Default DKIM selector",
type => 'string', format => 'dns-name', #see RFC6376 3.1
},
+ 'dkim-use-domain' => {
+ description => "Whether to sign using the address from the header or the envelope.",
+ type => 'string',
+ enum => [qw(header envelope)],
+ default => 'envelope',
+ },
};
}
@@ -152,6 +158,7 @@ sub options {
dkim_sign => { optional => 1 },
dkim_sign_all_mail => { optional => 1 },
dkim_selector => { optional => 1 },
+ 'dkim-use-domain' => { optional => 1 },
};
}
@@ -1808,6 +1815,7 @@ my $pmg_service_params = {
dkim_selector => 1,
dkim_sign => 1,
dkim_sign_all_mail => 1,
+ 'dkim-use-domain' => 1,
},
};
diff --git a/src/PMG/DKIMSign.pm b/src/PMG/DKIMSign.pm
index 08197f8..093ff34 100644
--- a/src/PMG/DKIMSign.pm
+++ b/src/PMG/DKIMSign.pm
@@ -2,6 +2,8 @@ package PMG::DKIMSign;
use strict;
use warnings;
+use Email::Address::XS qw(parse_email_addresses);
+use Email::Address::XS;
use Mail::DKIM::Signer;
use Mail::DKIM::TextWrap;
use Crypt::OpenSSL::RSA;
@@ -53,11 +55,16 @@ sub create_signature {
#determines which domain should be used for signing based on the e-mailaddress
sub signing_domain {
- my ($self, $sender_email) = @_;
-
- my @parts = split('@', $sender_email);
- die "no domain in sender e-mail\n" if scalar(@parts) < 2;
- my $input_domain = $parts[-1];
+ my ($self, $sender_email, $entity, $use_domain) = @_;
+
+ my $input_domain;
+ if ($use_domain eq 'header') {
+ $input_domain = parse_headers_for_signing($entity);
+ } else {
+ my @parts = split('@', $sender_email);
+ die "no domain in sender e-mail\n" if scalar(@parts) < 2;
+ $input_domain = $parts[-1];
+ }
if ($self->{sign_all}) {
$self->domain($input_domain) if $self->{sign_all};
@@ -84,8 +91,35 @@ sub signing_domain {
}
+sub parse_headers_for_signing {
+ # Following RFC 7489 [1], we only sign emails with exactly one sender in the
+ # From header.
+ #
+ # [1] https://datatracker.ietf.org/doc/html/rfc7489#section-6.6.1
+ my ($entity) = @_;
+
+ my $from_count = 0;
+ my $domain;
+
+ my @from_headers = $entity->head->get('from');
+ foreach my $from_header (@from_headers) {
+ my @addresses = parse_email_addresses($from_header);
+ $from_count += scalar(@addresses);
+ $domain = $addresses[0]->host() if scalar(@addresses) > 0;
+ }
+
+ die "there is more than one sender in the header" if $from_count > 1;
+ die "there is no sender in the header" if $from_count == 0;
+ return $domain;
+}
+
+
sub sign_entity {
- my ($entity, $selector, $sender, $sign_all) = @_;
+ my ($entity, $dkim, $sender) = @_;
+
+ my $sign_all = $dkim->{sign_all};
+ my $use_domain = $dkim->{use_domain};
+ my $selector = $dkim->{selector};
die "no selector provided\n" if ! $selector;
@@ -110,7 +144,7 @@ sub sign_entity {
$signer->extended_headers($extended_headers);
- if ($signer->signing_domain($sender)) {
+ if ($signer->signing_domain($sender, $entity, $use_domain)) {
$entity->print($signer);
my $signature = $signer->create_signature();
$entity->head->add('DKIM-Signature', $signature, 0);
diff --git a/src/PMG/RuleDB/Accept.pm b/src/PMG/RuleDB/Accept.pm
index 4ebd6da..d14c2fb 100644
--- a/src/PMG/RuleDB/Accept.pm
+++ b/src/PMG/RuleDB/Accept.pm
@@ -102,8 +102,7 @@ sub execute {
if ($dkim->{sign}) {
eval {
- $entity = PMG::DKIMSign::sign_entity($entity,
- $dkim->{selector}, $msginfo->{sender}, $dkim->{sign_all});
+ $entity = PMG::DKIMSign::sign_entity($entity, $dkim, $msginfo->{sender});
};
syslog('warning',
"Could not create DKIM-Signature - disabling Signing: $@") if $@;
diff --git a/src/PMG/RuleDB/BCC.pm b/src/PMG/RuleDB/BCC.pm
index 0f016f8..65b6fb5 100644
--- a/src/PMG/RuleDB/BCC.pm
+++ b/src/PMG/RuleDB/BCC.pm
@@ -142,8 +142,7 @@ sub execute {
my $dkim = $msginfo->{dkim} // {};
if ($dkim->{sign}) {
eval {
- $entity = PMG::DKIMSign::sign_entity($entity,
- $dkim->{selector}, $msginfo->{sender}, $dkim->{sign_all});
+ $entity = PMG::DKIMSign::sign_entity($entity, $dkim, $msginfo->{sender});
};
syslog('warning',
"Could not create DKIM-Signature - disabling Signing: $@") if $@;
diff --git a/src/bin/pmg-smtp-filter b/src/bin/pmg-smtp-filter
index dc7128c..6061459 100755
--- a/src/bin/pmg-smtp-filter
+++ b/src/bin/pmg-smtp-filter
@@ -636,6 +636,7 @@ sub handle_smtp {
my $dkim_sign = $msginfo->{trusted} && $pmg_cfg->get('admin', 'dkim_sign');
if ($dkim_sign) {
$msginfo->{dkim}->{sign} = $dkim_sign;
+ $msginfo->{dkim}->{use_domain} = $pmg_cfg->get('admin', 'dkim-use-domain');
$msginfo->{dkim}->{sign_all} = $pmg_cfg->get('admin', 'dkim_sign_all_mail');
$msginfo->{dkim}->{selector} = $pmg_cfg->get('admin', 'dkim_selector');
}
--
2.39.2
^ permalink raw reply [flat|nested] 2+ messages in thread
* [pmg-devel] applied: [PATCH pmg-api v8] fix #2971: DKIM: add setting to use From header when signing
2024-02-26 14:03 [pmg-devel] [PATCH pmg-api v8] fix #2971: DKIM: add setting to use From header when signing Maximiliano Sandoval
@ 2024-02-26 16:34 ` Stoiko Ivanov
0 siblings, 0 replies; 2+ messages in thread
From: Stoiko Ivanov @ 2024-02-26 16:34 UTC (permalink / raw)
To: Maximiliano Sandoval; +Cc: pmg-devel
applied with a few follow-ups on top, which hopefully make things clearer
(and shorter). Maybe you want to check them out.
tested this in my setup and managed to get a mail signed with this code
delivered to my private gmail inbox (through a few intermediate relays)
Thanks for seeing this through and the continued improvements!
On Mon, 26 Feb 2024 15:03:02 +0100
Maximiliano Sandoval <m.sandoval@proxmox.com> wrote:
> Following RFC 5322 [2], we add an option to use the address from the
> `From:` header instead of the Envelope From address.
>
> From RFC 7489 [1]:
>
> To illustrate, in relaxed mode, if a validated DKIM signature
> successfully verifies with a "d=" domain of "example.com", and the
> RFC5322.From address is "alerts@news.example.com", the DKIM "d="
> domain and the RFC5322.From domain are considered to be "in
> alignment". In strict mode, this test would fail, since the "d="
> domain does not exactly match the FQDN of the address.
>
> Tested with the following command:
>
> swaks --from foo@envelope.domain --to EMAIL -s PMG_ADDR:26 --data "Date: %DATE%\nTo: %TO_ADDRESS%\nFrom: bar@header.domain\nSubject: test %DATE%\nMessage-Id: <%MESSAGEID%>\nX-Mailer: swaks v%SWAKS_VERSION% jetmore.org/john/code/swaks/\n%NEW_HEADERS%\n%BODY%\n"
>
> Where EMAIL and PMG_ADDR are given adequate values, and envelope.domain
> and header.domain are in the 'Sign Domains' list.
>
> [1] https://datatracker.ietf.org/doc/html/rfc7489#section-3.1.1
> [2] https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2
>
> Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
> ---
> Differences from v7:
> - Simplify code counting the number of senders
> - `die` when the sender count is not 1
>
> Differences from v6:
> - Do not use uppercase in commit title
> - Rename dkim_use_domain to dkim-use-domain
>
> src/PMG/Config.pm | 8 +++++++
> src/PMG/DKIMSign.pm | 48 ++++++++++++++++++++++++++++++++++------
> src/PMG/RuleDB/Accept.pm | 3 +--
> src/PMG/RuleDB/BCC.pm | 3 +--
> src/bin/pmg-smtp-filter | 1 +
> 5 files changed, 52 insertions(+), 11 deletions(-)
>
> diff --git a/src/PMG/Config.pm b/src/PMG/Config.pm
> index 71d4c0b..0c5419e 100644
> --- a/src/PMG/Config.pm
> +++ b/src/PMG/Config.pm
> @@ -134,6 +134,12 @@ EODESC
> description => "Default DKIM selector",
> type => 'string', format => 'dns-name', #see RFC6376 3.1
> },
> + 'dkim-use-domain' => {
> + description => "Whether to sign using the address from the header or the envelope.",
> + type => 'string',
> + enum => [qw(header envelope)],
> + default => 'envelope',
> + },
> };
> }
>
> @@ -152,6 +158,7 @@ sub options {
> dkim_sign => { optional => 1 },
> dkim_sign_all_mail => { optional => 1 },
> dkim_selector => { optional => 1 },
> + 'dkim-use-domain' => { optional => 1 },
> };
> }
>
> @@ -1808,6 +1815,7 @@ my $pmg_service_params = {
> dkim_selector => 1,
> dkim_sign => 1,
> dkim_sign_all_mail => 1,
> + 'dkim-use-domain' => 1,
> },
> };
>
> diff --git a/src/PMG/DKIMSign.pm b/src/PMG/DKIMSign.pm
> index 08197f8..093ff34 100644
> --- a/src/PMG/DKIMSign.pm
> +++ b/src/PMG/DKIMSign.pm
> @@ -2,6 +2,8 @@ package PMG::DKIMSign;
>
> use strict;
> use warnings;
> +use Email::Address::XS qw(parse_email_addresses);
> +use Email::Address::XS;
> use Mail::DKIM::Signer;
> use Mail::DKIM::TextWrap;
> use Crypt::OpenSSL::RSA;
> @@ -53,11 +55,16 @@ sub create_signature {
>
> #determines which domain should be used for signing based on the e-mailaddress
> sub signing_domain {
> - my ($self, $sender_email) = @_;
> -
> - my @parts = split('@', $sender_email);
> - die "no domain in sender e-mail\n" if scalar(@parts) < 2;
> - my $input_domain = $parts[-1];
> + my ($self, $sender_email, $entity, $use_domain) = @_;
> +
> + my $input_domain;
> + if ($use_domain eq 'header') {
> + $input_domain = parse_headers_for_signing($entity);
> + } else {
> + my @parts = split('@', $sender_email);
> + die "no domain in sender e-mail\n" if scalar(@parts) < 2;
> + $input_domain = $parts[-1];
> + }
>
> if ($self->{sign_all}) {
> $self->domain($input_domain) if $self->{sign_all};
> @@ -84,8 +91,35 @@ sub signing_domain {
> }
>
>
> +sub parse_headers_for_signing {
> + # Following RFC 7489 [1], we only sign emails with exactly one sender in the
> + # From header.
> + #
> + # [1] https://datatracker.ietf.org/doc/html/rfc7489#section-6.6.1
> + my ($entity) = @_;
> +
> + my $from_count = 0;
> + my $domain;
> +
> + my @from_headers = $entity->head->get('from');
> + foreach my $from_header (@from_headers) {
> + my @addresses = parse_email_addresses($from_header);
> + $from_count += scalar(@addresses);
> + $domain = $addresses[0]->host() if scalar(@addresses) > 0;
> + }
> +
> + die "there is more than one sender in the header" if $from_count > 1;
> + die "there is no sender in the header" if $from_count == 0;
> + return $domain;
> +}
> +
> +
> sub sign_entity {
> - my ($entity, $selector, $sender, $sign_all) = @_;
> + my ($entity, $dkim, $sender) = @_;
> +
> + my $sign_all = $dkim->{sign_all};
> + my $use_domain = $dkim->{use_domain};
> + my $selector = $dkim->{selector};
>
> die "no selector provided\n" if ! $selector;
>
> @@ -110,7 +144,7 @@ sub sign_entity {
>
> $signer->extended_headers($extended_headers);
>
> - if ($signer->signing_domain($sender)) {
> + if ($signer->signing_domain($sender, $entity, $use_domain)) {
> $entity->print($signer);
> my $signature = $signer->create_signature();
> $entity->head->add('DKIM-Signature', $signature, 0);
> diff --git a/src/PMG/RuleDB/Accept.pm b/src/PMG/RuleDB/Accept.pm
> index 4ebd6da..d14c2fb 100644
> --- a/src/PMG/RuleDB/Accept.pm
> +++ b/src/PMG/RuleDB/Accept.pm
> @@ -102,8 +102,7 @@ sub execute {
>
> if ($dkim->{sign}) {
> eval {
> - $entity = PMG::DKIMSign::sign_entity($entity,
> - $dkim->{selector}, $msginfo->{sender}, $dkim->{sign_all});
> + $entity = PMG::DKIMSign::sign_entity($entity, $dkim, $msginfo->{sender});
> };
> syslog('warning',
> "Could not create DKIM-Signature - disabling Signing: $@") if $@;
> diff --git a/src/PMG/RuleDB/BCC.pm b/src/PMG/RuleDB/BCC.pm
> index 0f016f8..65b6fb5 100644
> --- a/src/PMG/RuleDB/BCC.pm
> +++ b/src/PMG/RuleDB/BCC.pm
> @@ -142,8 +142,7 @@ sub execute {
> my $dkim = $msginfo->{dkim} // {};
> if ($dkim->{sign}) {
> eval {
> - $entity = PMG::DKIMSign::sign_entity($entity,
> - $dkim->{selector}, $msginfo->{sender}, $dkim->{sign_all});
> + $entity = PMG::DKIMSign::sign_entity($entity, $dkim, $msginfo->{sender});
> };
> syslog('warning',
> "Could not create DKIM-Signature - disabling Signing: $@") if $@;
> diff --git a/src/bin/pmg-smtp-filter b/src/bin/pmg-smtp-filter
> index dc7128c..6061459 100755
> --- a/src/bin/pmg-smtp-filter
> +++ b/src/bin/pmg-smtp-filter
> @@ -636,6 +636,7 @@ sub handle_smtp {
> my $dkim_sign = $msginfo->{trusted} && $pmg_cfg->get('admin', 'dkim_sign');
> if ($dkim_sign) {
> $msginfo->{dkim}->{sign} = $dkim_sign;
> + $msginfo->{dkim}->{use_domain} = $pmg_cfg->get('admin', 'dkim-use-domain');
> $msginfo->{dkim}->{sign_all} = $pmg_cfg->get('admin', 'dkim_sign_all_mail');
> $msginfo->{dkim}->{selector} = $pmg_cfg->get('admin', 'dkim_selector');
> }
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2024-02-26 16:34 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-02-26 14:03 [pmg-devel] [PATCH pmg-api v8] fix #2971: DKIM: add setting to use From header when signing Maximiliano Sandoval
2024-02-26 16:34 ` [pmg-devel] applied: " Stoiko Ivanov
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal