Commit b6320c0b authored by John Levine's avatar John Levine
Browse files

working ARC seals

parent 89851383
......@@ -1799,7 +1799,7 @@ our @params = (
'default' => '3600',
},
{ 'gettext_id' => 'DKIM',
{ 'gettext_id' => 'DKIM and ARC',
'gettext_comment' =>
'DKIM signature verification and re-signing. It requires an external module: Mail-DKIM.',
},
......@@ -1862,7 +1862,7 @@ our @params = (
'optional' => '1',
'file' => 'sympa.conf',
},
{ 'name' => 'dkim_arc_feature',
{ 'name' => 'arc_feature',
'gettext_id' => 'Enable ARC',
'gettext_comment' =>
'If set to "on", Sympa may add ARC seals to outgoing messages.',
......@@ -1870,15 +1870,15 @@ our @params = (
'vhost' => '1',
'file' => 'sympa.conf',
},
{ 'name' => 'dkim_arc_srvid',
{ 'name' => 'arc_srvid',
'gettext_id' => 'SRV ID for Authentication-Results used in ARC seal',
'gettext_comment' =>
'Default is the domain used for ARC seals',
'Typically the domain of the mail server',
'vhost' => '1',
'optional' => '1',
'file' => 'sympa.conf',
},
{ 'name' => 'dkim_arc_signer_domain',
{ 'name' => 'arc_signer_domain',
'vhost' => '1',
'gettext_id' => 'The "d=" tag as defined in ARC',
'gettext_comment' =>
......@@ -1886,7 +1886,7 @@ our @params = (
'optional' => '1',
'file' => 'sympa.conf',
},
{ 'name' => 'dkim_arc_selector',
{ 'name' => 'arc_selector',
'gettext_id' => 'Selector for DNS lookup of ARC public key',
'gettext_comment' =>
'The selector is used in order to build the DNS query for public key. It is up to you to choose the value you want but verify that you can query the public DKIM key for "<selector>._domainkey.your_domain". Default is the same selector as for DKIM signatures',
......@@ -1894,7 +1894,7 @@ our @params = (
'optional' => '1',
'file' => 'sympa.conf',
},
{ 'name' => 'dkim_arc_private_key_path',
{ 'name' => 'arc_private_key_path',
'vhost' => '1',
'gettext_id' => 'File path for ARC private key',
'gettext_comment' =>
......
......@@ -528,7 +528,8 @@ sub _sanitize_changes_paragraph {
# the whole parameter instance is removed.
return (_pname($ppaths) => undef)
if grep {
$pitem->{format}->{$_}->{occurrence} =~ /^1/
not $pitem->{format}->{$_}->{obsolete}
and $pitem->{format}->{$_}->{occurrence} =~ /^1/
and not defined $cur->{$_}
} _keys($pitem->{format});
# If all children are removed, remove parent.
......
......@@ -8170,6 +8170,7 @@ sub _save_list_param {
} elsif (($pinfo->{$key}{'file_format'}{$k}{'occurrence'} =~ /n$/)
&& $pinfo->{$key}{'file_format'}{$k}{'split_char'}) {
next unless $p->{$k} and @{$p->{$k}};
$fd->print(
sprintf "%s %s\n",
......
......@@ -2153,9 +2153,20 @@ our %pinfo = (
'default' => {'conf' => 'dkim_signature_apply_on'}
},
'arc_parameters' => {
'arc_feature' => {
order => 70.04,
'group' => 'dkim',
'gettext_id' => "Add ARC seals to messages sent to the list",
'gettext_comment' =>
"Enable/Disable ARC. This feature requires Mail::DKIM::ARC to be installed, and maybe some custom scenario to be updated",
'format' => ['on', 'off'],
'occurrence' => '1',
'default' => {'conf' => 'arc_feature'}
},
'arc_parameters' => {
order => 70.05,
'group' => 'dkim',
'gettext_id' => "ARC configuration",
'gettext_comment' =>
'A set of parameters in order to define outgoing ARC seal',
......@@ -2167,7 +2178,7 @@ our %pinfo = (
"The file must contain a RSA pem encoded private key. Default is DKIM private key.",
'format' => '\S+',
'occurrence' => '0-1',
'default' => {'conf' => 'dkim_arc_private_key_path'}
'default' => {'conf' => 'arc_private_key_path'}
},
'arc_selector' => {
'order' => 2,
......@@ -2176,7 +2187,7 @@ our %pinfo = (
"The selector is used in order to build the DNS query for public key. It is up to you to choose the value you want but verify that you can query the public DKIM key for <selector>._domainkey.your_domain. Default is selector for DKIM signature",
'format' => '\S+',
'occurrence' => '0-1',
'default' => {'conf' => 'dkim_arc_selector'}
'default' => {'conf' => 'arc_selector'}
},
'arc_signer_domain' => {
'order' => 3,
......@@ -2186,22 +2197,14 @@ our %pinfo = (
'The ARC "d=" tag, is the domain of the sealing entity. The list domain MUST be included in the "d=" domain',
'format' => '\S+',
'occurrence' => '0-1',
'default' => {'conf' => 'dkim_arc_signer_domain'}
},
'arc_srvid' => {
'order' => 4,
'gettext_id' => 'SRV ID for Authentication-Results used in ARC seal',
'gettext_comment' => 'Default is the domain used for ARC seals',
'format' => '\S+',
'occurrence' => '0-1',
'default' => {'conf' => 'dkim_arc_srvid'}
'default' => {'conf' => 'arc_signer_domain'}
},
},
'occurrence' => '0-1'
},
'dkim_signature_apply_on' => {
order => 70.05,
order => 70.06,
'group' => 'dkim',
'gettext_id' =>
"The categories of messages sent to the list that will be signed using DKIM.",
......@@ -2218,7 +2221,7 @@ our %pinfo = (
},
'dmarc_protection' => {
order => 70.06,
order => 70.07,
'format' => {
'mode' => {
'format' => [
......
......@@ -534,12 +534,15 @@ BEGIN { eval 'use Mail::DKIM::Verifier'; }
sub check_dkim_signature {
my $self = shift;
$log->syslog('debug2', 'start %s', $Mail::DKIM::Verifier::VERSION);
return unless $Mail::DKIM::Verifier::VERSION;
my $robot_id =
(ref $self->{context} eq 'Sympa::List')
? $self->{context}->{'domain'}
: $self->{context};
$log->syslog('debug2', 'robot id %s', $robot_id);
return
unless Sympa::Tools::Data::smart_eq(
Conf::get_robot_conf($robot_id || '*', 'dkim_feature'), 'on');
......@@ -586,6 +589,158 @@ sub remove_invalid_dkim_signature {
}
}
BEGIN { eval 'use Mail::DKIM::ARC::Verifier'; eval 'use Mail::DKIM::ARC::Signer'; }
sub arc_seal {
$log->syslog('debug', '(%s)', @_);
my $self = shift;
my %options = @_;
my $arc_d = $options{'arc_d'};
my $arc_selector = $options{'arc_selector'};
my $arc_privatekey = $options{'arc_privatekey'};
my $arc_srvid = $options{'arc_srvid'};
my $arc_cv = $options{'arc_cv'};
unless ($arc_selector) {
$log->syslog('err',
"ARC selector is undefined, could not seal message");
return undef;
}
unless ($arc_privatekey) {
$log->syslog('err',
"ARC key file is undefined, could not seal message");
return undef;
}
unless ($arc_d) {
$log->syslog('err',
"ARC d= tag is undefined, could not seal message");
return undef;
}
unless ($arc_cv =~ m{^(none|pass|fail)$}) {
$log->syslog('err',
"ARC chain value %s is invalid, could not seal message", $arc_cv);
return undef;
}
unless ($Mail::DKIM::ARC::Signer::VERSION) {
$log->syslog('err',
"Failed to load Mail::DKIM::ARC::Signer Perl module, no seal added"
);
return undef;
}
unless ($has_mail_dkim_textwrap) {
$log->syslog('err',
"Failed to load Mail::DKIM::TextWrap Perl module, signature will not be pretty"
);
}
# DKIM::PrivateKey does never allow armour texts nor newlines. Strip them.
my $privatekey_string = join '',
grep { !/^---/ and $_ } split /\r\n|\r|\n/, $arc_privatekey;
my $privatekey = Mail::DKIM::PrivateKey->load(Data => $privatekey_string);
unless ($privatekey) {
$log->syslog('err', 'Can\'t create Mail::DKIM::PrivateKey');
return undef;
}
# create a signer object
my $arc = Mail::DKIM::ARC::Signer->new(
Algorithm => "rsa-sha256",
Chain => $arc_cv,
SrvId => $arc_srvid,
Domain => $arc_d,
Selector => $arc_selector,
Key => $privatekey,
);
unless ($arc) {
$log->syslog('err', 'Can\'t create Mail::DKIM::ARC::Signer');
return undef;
}
# $new_body will store the body as fed to Mail::DKIM to reuse it
# when returning the message as string. Line terminators must be
# normalized with CRLF.
# probably don't need this since DKIM just did it
my $msg_as_string = $self->as_string;
$msg_as_string =~ s/\r?\n/\r\n/g;
$msg_as_string =~ s/\r?\z/\r\n/ unless $msg_as_string =~ /\n\z/;
$arc->PRINT($msg_as_string);
unless ($arc->CLOSE) {
$log->syslog('err', 'Cannot ARC seal message');
return undef;
}
my ($dummy, $new_body) = split /\r\n\r\n/, $msg_as_string, 2;
$new_body =~ s/\r\n/\n/g;
# Seal is done. Rebuilding message as string with original body
# and new headers.
my @seal = $arc->as_strings();
foreach my $ahdr (@seal) {
my ($ah, $av) = split /:\s*/,$ahdr,2;
$self->add_header($ah, $av, 0);
}
$self->{_body} = $new_body;
delete $self->{_entity_cache}; # Clear entity cache.
return $self;
}
sub check_arc_chain {
my $self = shift;
$log->syslog('debug2', 'start %s', $Mail::DKIM::ARC::Verifier::VERSION);
return unless $Mail::DKIM::ARC::Verifier::VERSION;
my $robot_id =
(ref $self->{context} eq 'Sympa::List')
? $self->{context}->{'domain'}
: $self->{context};
my $srvid;
unless($srvid = Conf::get_robot_conf($robot_id || '*', 'arc_srvid')) {
$log->syslog('debug', 'ARC library installed, but no arc_srvid set');
return;
}
#$log->syslog('debug2', 'robot %s, srvid %s', $robot_id, $srvid);
# if there is no authentication-results, not much point in checking ARC
# since we can't add a new seal
my @ars = grep { m{^\s*\Q$srvid\E;} } $self->get_header('Authentication-Results');
unless(@ars) {
$log->syslog('debug2', 'ARC enabled but no Authentication-Results: %s;', $srvid);
return;
}
# already checked?
if($ars[0] =~ m{\barc=(pass|fail|none)\b}i) {
$log->syslog('debug2', 'ARC already %s', $1);
$self->{shelved}->{arc_cv} = $1;
return;
}
my $arc;
unless ($arc = Mail::DKIM::ARC::Verifier->new(Strict => 1)) {
$log->syslog('err', 'Could not create Mail::DKIM::ARC::Verifier');
return;
}
# Line terminators must be normalized with CRLF.
my $msg_as_string = $self->as_string;
$msg_as_string =~ s/\r?\n/\r\n/g;
$msg_as_string =~ s/\r?\z/\r\n/ unless $msg_as_string =~ /\n\z/;
$arc->PRINT($msg_as_string);
unless ($arc->CLOSE) {
$log->syslog('err', 'Cannot verify chain of (ARC) message');
return;
}
$log->syslog('debug2', 'result %s', $arc->result);
$self->{shelved}->{arc_cv} = $arc->result;
}
sub as_entity {
my $self = shift;
......@@ -3394,6 +3549,18 @@ I<Instance method>.
Verifies DKIM signatures included in the message,
and if any of them are invalid, removes them.
=item check_arc_chain ( )
I<Instance method>.
Checks ARC chain of the message
and sets {arc_cv} item of the message object.
=item arc_seal ( )
I<Instance method>.
Adds a new ARC seal if there's an arc_cv from check_arc_chain and
the cv is none or valid.
=item as_entity ( )
I<Instance method>.
......
......@@ -198,6 +198,8 @@ sub _twist {
$message->check_spam_status;
# Check DKIM signatures.
$message->check_dkim_signature;
# Check ARC seals
$message->check_arc_chain;
# Check S/MIME signature.
$message->check_smime_signature;
# Decrypt message. On success, check nested S/MIME signature.
......
......@@ -220,14 +220,17 @@ sub _twist {
# -5 S/MIME encryption
# -6 remove existing signature if altered
# -7 DKIM signing
# -8 ARC seal
if ($message->{shelved}{dmarc_protect}) {
$message->dmarc_protect;
}
my $dkim;
my ($dkim, $arc);
if ($message->{shelved}{dkim_sign}) {
$dkim = Sympa::Tools::DKIM::get_dkim_parameters($message->{context});
$arc = Sympa::Tools::DKIM::get_arc_parameters($message->{context})
if $message->{shelved}->{arc_cv};
}
if ( $message->{shelved}{merge}
......@@ -333,6 +336,16 @@ sub _twist {
'dkim_selector' => $dkim->{'selector'},
'dkim_privatekey' => $dkim->{'private_key'},
);
$new_message->arc_seal(
'arc_d' => $arc->{'d'},
'arc_selector' => $arc->{'selector'},
'arc_srvid' => $arc->{'srvid'},
'arc_privatekey' => $arc->{'private_key'},
'arc_cv' => $message->{shelved}->{arc_cv}
) if $arc;
delete $new_message->{shelved}{dkim_sign};
}
......@@ -382,6 +395,14 @@ sub _twist {
'dkim_selector' => $dkim->{'selector'},
'dkim_privatekey' => $dkim->{'private_key'},
);
$new_message->arc_seal(
'arc_d' => $arc->{'d'},
'arc_selector' => $arc->{'selector'},
'arc_srvid' => $arc->{'srvid'},
'arc_privatekey' => $arc->{'private_key'},
'arc_cv' => $message->{shelved}->{arc_cv}
) if $arc;
delete $new_message->{shelved}{dkim_sign};
}
......
......@@ -72,7 +72,6 @@ sub get_dkim_parameters {
Conf::get_robot_conf($robot_id, 'dkim_selector');
$keyfile = Conf::get_robot_conf($robot_id, 'dkim_private_key_path');
}
return undef
unless defined $data->{'d'}
and defined $data->{'selector'}
......@@ -90,6 +89,60 @@ sub get_dkim_parameters {
return $data;
}
sub get_arc_parameters {
$log->syslog('debug2', '(%s)', @_);
my $that = shift;
my ($robot_id, $list);
if (ref $that eq 'Sympa::List') {
$robot_id = $that->{'domain'};
$list = $that;
} elsif ($that and $that ne '*') {
$robot_id = $that;
} else {
$robot_id = '*';
}
my $data;
my $keyfile;
if ($list) {
# fetch arc parameter in list context
$data->{'d'} = $list->{'admin'}{'arc_parameters'}{'arc_signer_domain'}
|| $list->{'admin'}{'dkim_parameters'}{'signer_domain'};
$data->{'selector'} = $list->{'admin'}{'arc_parameters'}{'arc_selector'}
|| $list->{'admin'}{'dkim_parameters'}{'selector'};
$keyfile = $list->{'admin'}{'arc_parameters'}{'arc_private_key_path'}
|| $list->{'admin'}{'dkim_parameters'}{'private_key_path'};
} else {
# in robot context
$data->{'d'} = Conf::get_robot_conf($robot_id, 'arc_signer_domain')
|| Conf::get_robot_conf($robot_id, 'dkim_signer_domain');
$data->{'selector'} =
Conf::get_robot_conf($robot_id, 'arc_selector') ||
Conf::get_robot_conf($robot_id, 'dkim_selector');
$keyfile = Conf::get_robot_conf($robot_id, ' arc_private_key_path')
|| Conf::get_robot_conf($robot_id, 'dkim_private_key_path');
}
$data->{'srvid'} = Conf::get_robot_conf($robot_id, 'arc_srvid')
|| $data->{'d'};
return undef
unless defined $data->{'d'}
and defined $data->{'selector'}
and defined $keyfile;
my $fh;
unless (open $fh, '<', $keyfile) {
$log->syslog('err', 'Could not read arc private key %s: %m',
$keyfile);
return undef;
}
$data->{'private_key'} = do { local $RS; <$fh> };
close $fh;
return $data;
}
# Old name: tools::dkim_verifier().
#DEPRECATED: Use Sympa::Message::check_dkim_signature().
#sub verifier($msg_as_string);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment