OpenDKIM

来自 ArchWiki

OpenDKIMDomainKeys Identified Mail (DKIM) 发件人身份验证系统的开源实现。

包括 Yahoo、Google 和 Outlook.com 在内的大多数常用邮件提供商都支持 DKIM。

概念

基本上,DKIM 对来自服务器的所有消息进行数字签名,以验证消息实际上是从相关域名发送的,而不是伪造或修改的。

  • 发件人的邮件服务器使用私钥对外发电子邮件进行签名。
  • 当邮件到达时,接收者(或其服务器)从域名的 TXT 记录中读取公钥并验证签名。

这确保了消息是从私钥与域名公钥匹配的服务器发送的。

有关更多信息,请参见 RFC 6376

安装

安装 opendkim 软件包。

配置

签名服务的主要配置文件是 /etc/opendkim/opendkim.conf

  • 复制/移动示例配置文件 /usr/share/doc/opendkim/opendkim.conf.sample/etc/opendkim/opendkim.conf 并更改以下选项
/etc/opendkim/opendkim.conf
Domain                  example.com
KeyFile                 /path/to/keys/server1.private
Selector                myselector
Socket                  inet:8891@localhost
UserID                  opendkim
  • 套接字地址是在 /etc/postfix/main.cf 中指定的地址。以下是 /etc/postfix/main.cf 应包含的内容
/etc/postfix/main.cf
# For use by dkim milter
smtpd_milters = inet:localhost:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
  • 要生成密钥签名密钥,请指定用于发送邮件的域名和一个选择器,该选择器用于引用密钥。选择器可以是任何值。有关详细信息,请参见 RFC,但字母数字字符串应该可以
$ opendkim-genkey -r -s myselector -d example.com
  • 有时邮件在其传输过程中会被重新格式化(例如,制表符被空格替换),从而导致 DKIM 签名无效。为了防止标头和正文中的细微重新格式化破坏信任,存在规范化,这是一种声明格式化严格程度的策略。可用设置包括 simple(不允许重新格式化)和 relaxed(允许一些重新格式化)。有关详细信息,请参见 RFC 4871 3.4。这些可以为标头和正文单独设置
/etc/opendkim/opendkim.conf
...
Canonicalization        relaxed/simple
...

此示例允许对标头进行一些重新格式化,但不允许对消息正文进行重新格式化。openDKIM 的默认设置为 simple/simple

  • 还有其他配置选项可用。请务必阅读文档。
  • 启用/启动 opendkim.service

DNS 记录

添加带有选择器和公钥的 DNS TXT 记录。正确的记录是使用私钥生成的,可以在与私钥相同的位置的 myselector.txt 中找到。

示例

myselector._domainkey   IN	 TXT	"v=DKIM1; p=...................."

该记录还有其他几个可用开关(请参见 RFC 4871 3.6.1),最有趣的可能是 t=y,它启用测试模式,向检查接收者发出信号,表明邮件不得与未签名邮件区别对待,无论签名的状态如何。

检查 DNS 记录是否已正确更新

$ host -t TXT myselector._domainkey.example.com

您还可以使用网络上可用的 DKIM 密钥检查器 之一来检查 DKIM DNS 记录的格式是否正确。

Postfix 集成

将以下行添加到 main.cf

/etc/postfix/main.cf
non_smtpd_milters=inet:127.0.0.1:8891
smtpd_milters=inet:127.0.0.1:8891

要集成 DKIM 和 DMARC,请改用 unix 套接字,设置 UMask 以授予组对套接字的写入权限,并将 postfix 用户添加到 opendkim 组

/etc/opendkim/opendkim.conf
Socket                  local:/run/opendkim/opendkim.sock
UMask                   002
/etc/postfix/main.cf
non_smtpd_milters = unix:/run/opendkim/opendkim.sock,
                    unix:/run/opendmarc/opendmarc.sock
smtpd_milters = unix:/run/opendkim/opendkim.sock,
                unix:/run/opendmarc/opendmarc.sock

或在 master.cf 中更改 smtpd 选项

/etc/postfix/master.cf
smtp      inet  n       -       n       -       -       smtpd
  -o smtpd_client_connection_count_limit=10
  -o smtpd_milters=inet:127.0.0.1:8891

submission inet n       -       n       -       -       smtpd
  -o smtpd_enforce_tls=no
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_sasl_path=smtpd
  -o cyrus_sasl_config_path=/etc/sasl2
  -o smtpd_milters=inet:127.0.0.1:8891

Sendmail 集成

编辑 sendmail.mc 文件并添加以下行,在以 FEATURE 开头的最后一行之后

/etc/mail/sendmail.mc
INPUT_MAIL_FILTER(`opendkim', `S=inet:8891@localhost')

使用以下命令重建 sendmail.cf 文件

# m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf

然后重启 sendmail.service

多域名

要在同一服务器上为多个虚拟域名提供邮件服务器服务,请按如下所示修改基本配置

提供以下指令

/etc/opendkim/opendkim.conf
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts
注意: 每个 DKIM 密钥必须位于单独的域名文件夹中,例如 /etc/opendkim/keys/domain1.com/。否则会出现错误:“dkim: FAILED, invalid (public key: not available)” 错误消息以及 DKIM 电子邮件测试。但是,多个域名可以使用单个 DKIM 密钥。

创建以下两个文件,以告知 opendkim 在哪里可以找到正确的密钥。您可以为所有域名使用相同的密钥,也可以为每个域名生成一个密钥。进行更改以匹配您的设置。根据需要添加更多行。在以下示例中,example1.comexample2.com 共享同一个密钥。

/etc/opendkim/KeyTable
 
myselector._domainkey.example1.com example1.com:myselector:/etc/opendkim/keys/domain1.com/myselector.private
myselector._domainkey.example2.com example2.com:myselector:/etc/opendkim/keys/domain2.com/myselector.private
myselector._domainkey.example3.com example3.com:myselector:/etc/opendkim/keys/domain1.com/myselector.private
...
/etc/opendkim/SigningTable
*@example1.com myselector._domainkey.example1.com
*@example2.com myselector._domainkey.example2.com
*@example3.com myselector._domainkey.example3.com
...

现有的 /etc/opendkim/TrustedHosts 文件告诉 opendkim 谁可以使用密钥。这由配置文件中的 ExternalIgnoreList 指令引用。Opendkim 在验证传入邮件时会忽略此主机列表。

因为它也被 InternalHosts 指令引用,所以同一主机列表被视为“内部”,opendkim 会对其外发邮件进行签名。请记住将 <server_ip> 更改为正确的服务器 IP 地址

/etc/opendkim/TrustedHosts
127.0.0.1
::1
localhost
<server_ip>
hostname.example1.com
example1.com
hostname.example2.com
example2.com
...

将所有文件的所有权更改为 opendkim

# chown -R opendkim:mail /etc/opendkim

为每个域名添加带有您的选择器和公钥的 DNS TXT 记录。

您现在可以重启 opendkim。

安全

从安全角度来看,OpenDKIM 守护程序的默认配置不太理想(所有这些都是次要的安全问题)

  • OpenDKIM 守护程序根本不需要以 root 身份运行(前面建议的配置将使 OpenDKIM 自行放弃 root 权限,但 systemd 也可以做到这一点,而且更早)。
  • 如果您的邮件守护程序与 OpenDKIM 守护程序位于同一主机上,则不需要 localhost tcp 套接字,可以改用 unix 套接字,从而允许经典的用户/组访问控制。
  • OpenDKIM 默认使用 /tmp 文件夹,而它可以使用自己的文件夹,并具有额外的访问限制。
注意: 此示例适用于单域名设置。

以下配置文件将修复大多数这些问题(假设您正在使用 Postfix),并在 systemd 服务单元中删除一些不必要的选项

/etc/opendkim/opendkim.conf
BaseDirectory           /var/lib/opendkim
Domain                  example.com
KeyFile                 /etc/opendkim/myselector.private
Selector                myselector
Socket                  local:/run/opendkim/opendkim.sock
Syslog                  Yes
TemporaryDirectory      /run/opendkim
UMask                   002
/etc/systemd/system/opendkim.service
[Unit]
Description=OpenDKIM daemon
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
User=opendkim
Group=postfix
ExecStart=/usr/bin/opendkim -x /etc/opendkim/opendkim.conf
RuntimeDirectory=opendkim
RuntimeDirectoryMode=0750

[Install]
WantedBy=multi-user.target

相应地编辑 /etc/postfix/main.cf 以使 Postfix 监听此 unix 套接字

/etc/postfix/main.cf
smtpd_milters = unix:/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock

故障排除

错误: "milter-reject: END-OF-MESSAGE from localhost"

很可能是 Postfix milter 协议在 /etc/postfix/main.cf 中设置错误

# Postfix ≥ 2.6
milter_protocol = 6
# 2.3 ≤ Postfix ≤ 2.5
milter_protocol = 2

Authentication-Results: "dkim=neutral (bad format) header.i=@example.com"

很可能是由于给定选择器的 DNS TXT 记录被拆分为三个或更多资源记录 (RR) 引起的。验证器在连接记录时获得了错误的记录值。

例如,给定选择器 "default._domainkey.example.com" 的 DNS TXT 记录,其具有正确的预期值

"v=DKIM1; k=rsa; s=email; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrXzI8BMAv3rTYU9FA4F1m2aLyT7JF8qnhTuqWibR/X55ZxoUX8fceXkRbM03tgn+1UWo5mbNN5siLPDlNOKU6fWCmkCbroPXe0vpip72zkFCtYxO4NTQY0kVaKVyFpUbFbxN3oabYTmaty3eE2yQDDAmJeZiVyEE7K7E0vnW9KpiJypFPFoft52Dqr3BTB8197gHPEMXgeP5gYkjJxVEfJZiZVco6p41JUr0CzD2dPun6pSLOO8NCkx3bWNKsL1DA7CR6qX/o2oOsd821N+0tn+8oc6x0rnhetaR0442NAGzxna4jTkUe9jwAK4aU7nKQxqNn/wOw1K2qT7uhsVMwIDAQAB".

另一端的验证器接收到的记录被拆分为三个,

$ resolver -t TXT default._domainkey.example.com
= options: &{sqtype:TXT sqclass:IN nameserver:udp://127.0.0.1 insecure:false qname:default._domainkey.example.com qtype:16 qclass:1}
= resolv.conf: &{Domain:localhost Search:[] NameServers:[127.0.0.1] NDots:1 Timeout:5 Attempts:2 OptMisc:map[]}
> Lookup default._domainkey.example.com at 127.0.0.1:53
< From: 127.0.0.1:53
> Header: {ID:2136 IsQuery:false Op:0 IsAA:false IsTC:false IsRD:true IsRA:true RCode:0 QDCount:1 ANCount:3 NSCount:0 ARCount:0}
> Question: &{Name:default._domainkey.example.com Type:TXT}
> Status: OK
> Answer #1:
>> Resource record: {Name:default._domainkey.example.com Type:16 Class:1 TTL:1822 rdlen:143}
>> RDATA: YkjJxVEfJZiZVco6p41JUr0CzD2dPun6pSLOO8NCkx3bWNKsL1DA7CR6qX/o2oOsd821N+0tn+8oc6x0rnhetaR0442NAGzxna4jTkUe9jwAK4aU7nKQxqNn/wOw1K2qT7uhsVMwIDAQAB
> Answer #2:
>> Resource record: {Name:default._domainkey.example.com Type:16 Class:1 TTL:1822 rdlen:253}
>> RDATA: p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrXzI8BMAv3rTYU9FA4F1m2aLyT7JF8qnhTuqWibR/X55ZxoUX8fceXkRbM03tgn+1UWo5mbNN5siLPDlNOKU6fWCmkCbroPXe0vpip72zkFCtYxO4NTQY0kVaKVyFpUbFbxN3oabYTmaty3eE2yQDDAmJeZiVyEE7K7E0vnW9KpiJypFPFoft52Dqr3BTB8197gHPEMXgeP5g
> Answer #3:
>> Resource record: {Name:default._domainkey.example.com Type:16 Class:1 TTL:1822 rdlen:26}
>> RDATA: v=DKIM1; k=rsa; s=email;

当验证器组合记录时,其获得的值为,

YkjJxVEfJZiZVco6p41JUr0CzD2dPun6pSLOO8NCkx3bWNKsL1DA7CR6qX/o2oOsd821N+0tn+8oc6x0rnhetaR0442NAGzxna4jTkUe9jwAK4aU7nKQxqNn/wOw1K2qT7uhsVMwIDAQABp=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrXzI8BMAv3rTYU9FA4F1m2aLyT7JF8qnhTuqWibR/X55ZxoUX8fceXkRbM03tgn+1UWo5mbNN5siLPDlNOKU6fWCmkCbroPXe0vpip72zkFCtYxO4NTQY0kVaKVyFpUbFbxN3oabYTmaty3eE2yQDDAmJeZiVyEE7K7E0vnW9KpiJypFPFoft52Dqr3BTB8197gHPEMXgeP5gv=DKIM1; k=rsa; s=email;

解决方案:生成密钥长度为 1024 位(或小于 2048 位)的密钥,使其适合 DNS TXT 记录上的 255 个字符。

注释

在您即将与垃圾邮件作斗争并提高人们对您服务器的信任时,您可能需要查看 Sender Policy Framework,这基本上意味着添加一个 DNS 记录,声明哪些服务器有权为您的域名发送电子邮件。

参见