14
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【学習メモ】DMARCとDKIMを試す

Last updated at Posted at 2022-07-11

はじめに

メールを送るのにはとりあえずSPFがPassすれば良い、と思っていましたが、
DKIMやDMARCがあるけど面倒だなと思っていて避けてきました。

本当に面倒なのか、そもそも何を設定すれば良いのか、何ができるのか、確認します。

下記ページで紹介されている手続きを参考に実施しました。
DKIM の署名を手動で付与してみる(tech.quickguard.jp)

途中、上記事にならいDKIMを手動で署名して送る記載がありますが、本記事の通りだとうまく行きません。
後日時間があれば修正します。

SPFとDKIMとDMARC

試したあとに理解した内容を記載しています。

誰が何をするのか?

忘れてしまうので確認しておきましょう。

送信元のドメイン所有者 正規の送信者 パスさせようとする非正規の送信者(例) 受信者
SPF 送信元として信頼して良い送信メールサーバーの情報を、送信に使用するFQDNのSPFレコード(TXT)に追加しておく SPFレコードに記載したホストからメールを送信する 1.SPFレコードに踏み台にするメールサーバーの情報を混入させて送信する
or
2.SPFレコードに記載のあるホストからメールを送る
実際に送られてきたIP/ホストが、MAIL FROM(エンベロープFrom)のドメインのDNSにあるか検証する。(RFC7208ではHELOもチェックすることを推奨とあるが受信側の実装による)
DKIM(作成者署名) 署名に使う公開鍵をDNSに登録しておく(ドメインタグ、セレクタタグ) タグ、利用するヘッダを決め、秘密鍵で署名して送る 1.窃取した秘密鍵を使って暗号化して送信する
or
2.DNSレコードを侵害して別の公開鍵を登録しその秘密鍵で署名して送る
ドメインタグ・セレクタタグから公開鍵を取得し、DKIM署名を検証する。
DMARC DMARCのポリシーを決めDNSレコード(TXTレコード)に追加する 0.DMARCのポリシーに従いレポートを記録できる環境を整える
and
1.SPF/DKIMに適合する条件で送信する
SPFまたはDKIMを成功させるメールを送る 1.SPF/DKIMの取り扱いをDMARCのレコードから取得する
and
2.SPF/DKIMがパスしているかチェックする(どちらかで良い)
and
3. 1.で取得したDMARCのポリシーに従い本文のFrom(エンペローブFromではない)と1.でチェックしたドメインが一致しているか(SPFはドメイン、DKIMはiタグ)検証する。

SPF

SPFは、ドメイン所有者がTXTレコードにSPFレコードとして、承認されたメールサーバーを記載しておくことで、そのドメインから送られたメールの受信者がSPFレコードと送信メールのヘッダを比較することで、承認されたサーバーから送られたメールか確認できます。

参考文献
SPFを設定しようとしてタイプにSPFを使用してはいけない(TXTを使用する)(uunfo.hatenablog.com)
RFC 7208 - 電子メール、バージョン1でのドメインの使用を承認するためのSender Policy Framework(SPF)(tex2e.github.io)

DKIM

DKIMは、送信側が公開鍵をDNSに登録しておきます。
送信者は対する秘密鍵で送信メールにDKIM署名を付与して送ることで、受信側がDNSに登録された公開鍵を用いてDKIM署名が正しいかをチェックし、メールの内容が改ざんされていないか、送信元(経由元)に正当性があるか(正しい秘密鍵を持っているか)を確認します。
送信元で付与する作成者署名と、経由元で署名する第三者署名の2種類があります。
作成者署名でないとDMARCはパスしたとみなされません。
RFC 6376 - DomainKeys Identified Mail(DKIM)署名(tex2e.github.io)

DMARC

ドメインのセキュリティと、メール送信の信頼性を強化する規格とのことで、
① ドメインの所有者が、認証失敗時に受信者がどのようにメールを取り扱うか方法を指定できる(DMARCポリシー)
② ドメインの所有者が、メールの認証結果を受信者から受け取れる(DMARCレポート)
この2つがあります。
特に①は、SPFまたはDKIMのどちらかがパスし、かつパスした方で検証に使われたドメインが、メール本文のFROMのドメインと一致しているかを確認します。
SPF+DMARCだけであればDNSの設定だけで済み、かなり強力な対策となります。
(SPF -> エンベロープFrom、DMARC -> ヘッダーFromをチェック)

参考資料
【図解】DKIMとは?SPFとの違い、最新のDMARCも!なりすましメール対策の仕組みを解説(www.kagoya.jp)
RFC 7489 - ドメインベースのメッセージ認証、レポート、および準拠(DMARC)(tex2e.github.io)

DKIM事前学習

DKIMは、送信側がメールヘッダに署名を追加し、受信側がメールヘッダとDNSの鍵情報で署名が正しい=秘匿情報を持つ送信者が送信していることを確認するものです。
どういった署名がつけられるのか確認します。

DKIM署名の理解

RCF6376の3.5 DKIM署名のヘッダーフィールドを見てみます。例がありましたので、その例を見ながら必要なフィールドを確認します。

DKIM-Signature: v=1; a=rsa-sha256; d=example.net; s=brisbane;
      c=simple; q=dns/txt; i=@eng.example.net;
      t=1117574938; x=1118006938;
      h=from:to:subject:date;
      z=From:foo@eng.example.net|To:joe@example.com|
       Subject:demo=20run|Date:July=205,=202005=203:44:08=20PM=20-0700;
      bh=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=;
      b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZVoG4ZHRNiYzR
タグの種類 Value 内容
sig-v-tag v=1 DKIM鍵レコードのバージョン。デフォルトは"DKIM1"
sig-a-tag a=rsa-sha256 同RFCではSHA1とSHA256の2が定義されていて、可能な限りはSHA256を使用します。
sig-b-tag b=dzdVy..... base64で記述された署名データです。
sig-bh-tag bh=MTIzN..... base64で記述された、正規化されたBODYパートのハッシュ値です。
sig-c-tag c=simple メッセージの正規化の方法です。ヘッダだと、simpleは大文字小文字を区別せず、relaxedはヘッダーフィールド名を小文字に変換し末尾のスペースまたはタブ文字を削除します。本文の正規化の方法もそれぞれ異なります。
sig-d-tag d=example.net 署名ドメイン識別子(Signing Domain Identifier / SDID)を記述します。
sig-h-tag h=from:to:subject:date 署名されたヘッダーフィールドを順に列挙します。DKIM署名自体のフィールドは含めません。
sig-i-tag i=@eng.example.net The Agent or User Identifier (AUID)。ユーザー識別子となっていますが、local-partなしの@以降が記載されているようです。
sig-l-tag (なし) 本文の長さのカウントで10進数で記載されます。8.2セクションで注意があり、攻撃可能な部分になるため、評価者は無視するのが良い(wish to)とあるため、あえて使う必要はなさそうです。
sig-q-tag q=dns/txt 公開鍵の取得に使用されるクエリメソッドのコロンで区切られたリストです。デフォルトはdns/txtで、有効な値はそれしかありません。
sig-s-tag s=brisbane ドメインタグ(d=)の名前空間を細分化するセレクタです。同じドメインでも複数の鍵を使い分けたいときは、ここで設定します。
sig-t-tag t=1117574938 署名のタイムスタンプ。UTCで1970年1月1日00:00:00からの秒数。
sig-x-tag x=1118006938 署名の有効期限で、デフォルトは有効期限なしです。形式はtタグと同じで、tタグの値よりも大きい必要があります。
sig-z-tag z=From:foo... コピーされたヘッダーフィールドです。署名時に存在するすべてのヘッダーフィールドを含めたり、hタグと同じにしたりする必要はありません。

s-tagが分かりづらいですが、d-tagでexample.jpを指定し、s-tagでikka.soumuを指定しておくと、ikka.soumu._domainkey.example.jpに鍵を公開することができます。メールでいう@前のアカウントや、@以降のメールサーバーを示すFQDNとは直接関係はないようです。

DKIMハンズオン

実際にDKIMが通るようにします。
まずは送信者側の準備を行います。

ドメインの準備

.jpや.comなどテストに利用できるドメインを取得します。
送信元をexample.com、送信先をgmail.comとドメイン名を仮定して記述します。
追試する場合は適宜置き換えてください。
その後のDMARCではもう一つexample.jpというドメインを準備して使います。

鍵の準備

メールの署名と検証には公開鍵暗号方式の鍵が使われるということで、秘密鍵と公開鍵のキーペアを作ります。
まずは秘密鍵から。

# mkdir /usr/local/src/dkim
# cd /usr/local/src/dkim
# openssl genrsa -out ./rsa.private 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
...........................+++++
...+++++
e is 65537 (0x010001)

# head -2 rsa.private
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAn+8EY1xh+DqrWUwM36b1f8+2YfbaMEuRhgsbiKpH9xUn5WK1

# chmod 644 rsa.private

公開鍵を作ります。

# openssl rsa -pubout < rsa.private > rsa.public
writing RSA key

# cat rsa.public
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn+8EY1xh+DqrWUwM36b1
f8+2YfbaMEuRhgsbiKpH9xUn5WK1Vd9uD0Jq+StTjCXhigLJMvJRu8Pda6KkrGJY
4oA4si0SwNPoHTK1Emlsu9PDfqWNmfpYpOLMdU8kCmCNuSviSJZsFZrPkAm3H2ob
onQmim1YLe/Mr55fRNuQMSi1tFS7u5XXkpSKfbcwhanAv3+vMekpnFf9dJ5QwSyV
qVBPyj94JPagGdcNMzdFimIjwjwL9MTnTO6uqyOwPVDHITOE/GAF5sW0RIhwGGnp
14uQz5O5nYvU+M6uQLlnzXSUQ5pUDEoLrC0dZSXpvNekHVn3Bxjg3Kut1YVTfDfs
pwIDAQAB
-----END PUBLIC KEY-----

DNSの準備

送信元ドメインの検証ができるよう、ここではexample.comに公開鍵を登録します。
先の記事では、TXTレコードとして公開鍵の各行をRRとしてセットしていました。
RCF6376の3.6 Key Management and Representationを参照しながら
同じように公開鍵をRRとしてセットしてみます。
s-tagがrequiredなので、sampleとしてみます。
そのため作るレコードはsample._domainkey.example.comのTXTレコードです。

Route53への登録例を示します。

OK例(テキストボックスに貼り付ける中身)
"v=DKIM1;h=sha256;k=rsa;t=y;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn+8EY1xh+DqrWUwM36b1f8+2YfbaMEuRhgsbiKpH9xUn5WK1Vd9uD0Jq+StTjCXhigLJMvJRu8Pda6KkrGJY4oA4si0SwNPoHTK1Emlsu9PDfqWNmfpYpOLMdU8kCmCNuSviSJZsFZrPkAm3H2ob" "onQmim1YLe/Mr55fRNuQMSi1tFS7u5XXkpSKfbcwhanAv3+vMekpnFf9dJ5QwSyVqVBPyj94JPagGdcNMzdFimIjwjwL9MTnTO6uqyOwPVDHITOE/GAF5sW0RIhwGGnp14uQz5O5nYvU+M6uQLlnzXSUQ5pUDEoLrC0dZSXpvNekHVn3Bxjg3Kut1YVTfDfspwIDAQAB"
OK例
$ dig sample._domainkey.example.com
(略)
;; ANSWER SECTION:
sample._domainkey.example.com.	60 IN	TXT	"v=DKIM1;h=sha256;k=rsa;t=y;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn+8EY1xh+DqrWUwM36b1f8+2YfbaMEuRhgsbiKpH9xUn5WK1Vd9uD0Jq+StTjCXhigLJMvJRu8Pda6KkrGJY4oA4si0SwNPoHTK1Emlsu9PDfqWNmfpYpOLMdU8kCmCNuSviSJZsFZrPkAm3H2ob" "onQmim1YLe/Mr55fRNuQMSi1tFS7u5XXkpSKfbcwhanAv3+vMekpnFf9dJ5QwSyVqVBPyj94JPagGdcNMzdFimIjwjwL9MTnTO6uqyOwPVDHITOE/GAF5sW0RIhwGGnp14uQz5O5nYvU+M6uQLlnzXSUQ5pUDEoLrC0dZSXpvNekHVn3Bxjg3Kut1YVTfDfspwIDAQAB"
NG例(テキストボックスに貼り付ける中身)
v=DKIM1;h=sha256;k=rsa;t=y;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn+8EY1xh+DqrWUwM36b1
f8+2YfbaMEuRhgsbiKpH9xUn5WK1Vd9uD0Jq+StTjCXhigLJMvJRu8Pda6KkrGJY
4oA4si0SwNPoHTK1Emlsu9PDfqWNmfpYpOLMdU8kCmCNuSviSJZsFZrPkAm3H2ob
onQmim1YLe/Mr55fRNuQMSi1tFS7u5XXkpSKfbcwhanAv3+vMekpnFf9dJ5QwSyV
qVBPyj94JPagGdcNMzdFimIjwjwL9MTnTO6uqyOwPVDHITOE/GAF5sW0RIhwGGnp
14uQz5O5nYvU+M6uQLlnzXSUQ5pUDEoLrC0dZSXpvNekHVn3Bxjg3Kut1YVTfDfs
pwIDAQAB
NG例
$ dig sample._domainkey.example.com
(略)
;; ANSWER SECTION:
sample._domainkey.example.com.	60 IN	TXT	"14uQz5O5nYvU+M6uQLlnzXSUQ5pUDEoLrC0dZSXpvNekHVn3Bxjg3Kut1YVTfDfs"
sample._domainkey.example.com.	60 IN	TXT	"4oA4si0SwNPoHTK1Emlsu9PDfqWNmfpYpOLMdU8kCmCNuSviSJZsFZrPkAm3H2ob"
sample._domainkey.example.com.	60 IN	TXT	"f8+2YfbaMEuRhgsbiKpH9xUn5WK1Vd9uD0Jq+StTjCXhigLJMvJRu8Pda6KkrGJY"
sample._domainkey.example.com.	60 IN	TXT	"onQmim1YLe/Mr55fRNuQMSi1tFS7u5XXkpSKfbcwhanAv3+vMekpnFf9dJ5QwSyV"
sample._domainkey.example.com.	60 IN	TXT	"pwIDAQAB"
sample._domainkey.example.com.	60 IN	TXT	"qVBPyj94JPagGdcNMzdFimIjwjwL9MTnTO6uqyOwPVDHITOE/GAF5sW0RIhwGGnp"
sample._domainkey.example.com.	60 IN	TXT	"v=DKIM1;h=sha256;k=rsa;t=y;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn+8EY1xh+DqrWUwM36b1"

正しく入力すると、Answer Sectionは1つのレコードが返ってきます。
NG例ように、公開鍵が複数のレコードとなり返ってきてしまう場合、正しくDKIMが設定されていません。

今回はAmazon Route53を利用したので以下の記事を参考に登録しました。
DKIM 構文を使用して TXT レコードを作成しようとしたときに表示される「CharacterStringTooLong (Value is too long) encountered with {Value}」({Value} で発生した CharacterStringTooLong (値が長すぎます)) エラーの解決方法を教えてください。

% dig sample._domainkey.example.com +short

Memo
DNSのUDPは512バイトでは、と思っていましたが、2022年現在、RFC6891では4096オクテット、RFC4035では最低1220で4000オクテットもサポートスべきとありました。
TXTレコード自体は、RFC1035で256文字までと定義されています。
AWSだとTXTレコードの上限は4000文字、1レコード(1列)で255文字が最大になっているようです。

512バイトについての参考資料
DNSのメッセージサイズについて考える
~ランチのおともにDNS~(jprs.jp)

Route 53 で 255 文字を超える SPF レコードまたは TXT レコードを設定するにはどうすればよいですか?
(aws.amazon.com)

SPFも通るように送信元のメールサーバーの情報も追加しておきます。

SPFレコードの追加例
$ dig example.com txt +short | grep spf
"v=spf1 +ip4:xxx.xxx.xxx.xxx -all"

電子署名の作成

メールを作成し、電子署名を追加します。
sig-h-tagに、電子署名を生成するために使われるヘッダーフィールドを入れます。
電子署名の計算が面倒になるので、ここではREQUIEDのfromsubjectを使います。

本文のハッシュの作成

bhタグには、本文をハッシュ化した値を入れます。

今回はRFC6376の3.4.2に従いrelaxedでヘッダーを正規化します。
h=from;としたいので、FROMのみを残して正規化します。

メールのヘッダの内容
FROM: postmaster@example.com
TO: postmaster@gmail.com
SUBJECT: DKIMtestmail
relaxedで正規化したヘッダ
from: postmaster@example.com
本文の作成とハッシュ化

本文はbhタグにハッシュ値を入れる必要があるので、正規化後にハッシュ化します。
今回は正規化しても前と後が変わらない文字列を本文とします。

$ echo "This is a DKIM test mail." > can-body.txt

ハッシュ化します。

$ cat can-body.txt | openssl dgst -sha256 -binary | base64
wQqA2fSAT+KHt33FOsvAMaVYpbHj2eycB74VA0T6S0I=
署名の作成

3.7の最後の方に署名の内容について書いてありますが、難解なので記事通りに進めます。
今回hタグではFromのみを署名の対象にしていますので、h=from;となります。
本文に関するタグは先ほどのハッシュ値を入れてbh=wQqA2fSAT+KHt33FOsvAMaVYpbHj2eycB74VA0T6S0I=;となります。
上記から、署名データは以下のようになります。
3.5ではメッセージに含まれないフィールドを含めて良いとありますが、今回はhタグで指定したヘッダのみ使います。

$ cat << EOF > dkimsign.txt
from: postmaster@example.com
dkim-signature: v=1;a=rsa-sha256; bh=wQqA2fSAT+KHt33FOsvAMaVYpbHj2eycB74VA0T6S0I=; c=relaxed/relaxed; d=example.com; h=from; s=sample; t=1656514800;b=
EOF

without a trailing CRLFとありますので、Windowsを使っている場合はb=のあとにCRLFがある場合削除する必要があります。

sig-b-tagに入れるハッシュ値を計算します。

$ cat dkimsign.txt | openssl dgst -sha256 -sign /usr/local/src/dkim/rsa.private | base64
Bh+o7cH6TVGHTUgzO4vaBtFE0Tnxzyfg5w+1BJ9wVLOT9eD90kUcPnGa89ar+8DBjkQ6mdcvoUK4
TLTLDySVBmhv8fIqwkCa4fPEPXM0wk5YqZ5GR84q2V+gaEw8JRQHBEHEB4JngvUsR+bvMBrzNarg
0dBwBaGOLLT3DMvJ+cwRNmHIX9itHHuaY8UyCLX+JyoEDVXO1YBnvPTeK+cHJqHJaJAPKtEUOJ+L
3C24v9fvL/ECehNua9HwMI1W+CujSX8k+JYgCchxwcUbMYG/yP4Z7KL0uD5GrdAJlJ1FVu+dUp2p
phe5epTre4CBXhRRZN3rhiBb/DmdsGYNyjiiXA==

b-tagが完成したので、DKIMの署名情報を整理し、メールを形成します。

念の為公開鍵で確認しておきます。

$ openssl dgst -sha256 -sign /usr/local/src/dkim/rsa.private dkimsign.txt| base64 > dkimsign.sig

$ openssl dgst -SHA256 -verify /usr/local/src/dkim/rsa.public -signature dkimsign.sig dkimsign.txt
$ cat << EOF > message.txt
DKIM-Signature: v=1; a=rsa-sha256;
 bh=wQqA2fSAT+KHt33FOsvAMaVYpbHj2eycB74VA0T6S0I=;
 c=relaxed/relaxed; d=example.com;
 h=from; s=sample;
 t=1656514800;
 b=Bh+o7cH6TVGHTUgzO4vaBtFE0Tnxzyfg5w+1BJ9wVLOT9eD90kUcPnGa89ar+8DBjkQ6mdcvoUK4
  TLTLDySVBmhv8fIqwkCa4fPEPXM0wk5YqZ5GR84q2V+gaEw8JRQHBEHEB4JngvUsR+bvMBrzNarg
  0dBwBaGOLLT3DMvJ+cwRNmHIX9itHHuaY8UyCLX+JyoEDVXO1YBnvPTeK+cHJqHJaJAPKtEUOJ+L
  3C24v9fvL/ECehNua9HwMI1W+CujSX8k+JYgCchxwcUbMYG/yP4Z7KL0uD5GrdAJlJ1FVu+dUp2p
  phe5epTre4CBXhRRZN3rhiBb/DmdsGYNyjiiXA==
EOF

unixtimeの1656514800は東京の2022/6/30 0:00:00です。
DKIMのタグはインデントしておかないと送信時に本文とみなされてしまうのでインデントが必要です。

$ cat << EOF >> message.txt
From: postmaster@example.com
To: postmaster@gmail.comm
Subject: DKIMtestmail

This is a DKIM test mail.
EOF

メッセージが完成しました。

$ cat message.txt
DKIM-Signature: v=1; a=rsa-sha256;
 bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=;
 c=relaxed/relaxed; d=example.com;
 h=from:subject; s=sample;
 t=1656514800;
 b=FP253yB5K5K9zrgLo+oT3Ots39/BV0ND43IBAiSUMHdNTlGBxhKtGzS33jjxjd8l9NQOjDvwLhNq
  sOQAmjIWVXi+uKeahbQa4ogktJg52DBXDcvhjk0rteZF0LorvGtBw/cGl7Domml+VPgMGZlQryau
  yOZyfrXja5UB5TgPuu2FZdEeQqxEplZ3AgaL32J2E49SJJuSWNTmrADkPIvrAsMlZq8zqZqAjjy6
  exsYAovU5xrJ8/2qvFdFYdYUkmlv7+sSdi+1kmWrCuHK1OtYADwbVQKOg1TK5o5EdrPUsHC3FiYe
  MVXPE+abd5RDJOne7Mbu5IyF29hTkZwT3JbujQ==
From: postmaster@example.com
To: postmaster@gmail.comm
Subject: DKIMtestmail

This is a DKIM test mail.

送信テスト

送ってみます。

$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 xxxxxxxxxxxxxx ESMTP Postfix
HELO example.com
250-xxxxxxxxxxxxxx
MAIL FROM: postmaster@example.com
250 2.1.0 Ok
RCPT TO: postmaster@gmail.comm(宛先は変えること)
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
(message.txtの中身を貼り付け)
.
250 2.0.0 Ok: queued as 1EBAD6399C
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

Gmail側でdkim=neutral (body hash did not verify)と出て、DKIM FAILとなってしまいました。
どこかに問題があるようです。手順に誤りがありましたら追って訂正します。
(実際にテストした値は独自ドメイン+ドメインのFrom/Toにあわせて変えており、DKIMCheckerツールを使ったDNSレコードの検証を行っています。)

OpenDKIMで対応

DMARCを試すためにはDKIMが成功していなければならないので、OpenDKIMで自動的に署名してもらいます。
CentOS で postfix に DKIM をインストールする(pgmemo.tokyo)
上記を設定し、Postfix+OpenDKIMで署名するとうまくいきました。

# dnf install -y opendkim

/etc/opendkim.confのうち
Mode vMode svに変更
KeyFile /etc/opendkim/keys/default.privateをコメントアウト
#KeyTable /etc/opendkim/KeyTableを有効化
#SigningTable refile:/etc/opendkim/SigningTableを有効化
#InternalHosts refile:/etc/opendkim/TrustedHostsを有効化

# echo "sample._domainkey.example.com example.com:sample:/usr/local/src/dkim/rsa.private" \
>> /etc/opendkim/KeyTable
# echo "*@example.com  sample._domainkey.example.com" \
>> /etc/opendkim/SigningTable
# systemctl start opendkim
# cat << EOF >> /etc/postfix/main.cf
# dkim
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = \$smtpd_milters
milter_default_action = accept
EOF
# systemctl restart postfix

同じようにtelnetでメール送信すると成功することが確認できます。
Postfix側で自動的に署名してくれるため、DATAにDKIM署名は無くて問題ありません。

DMARC事前学習

DKIMが成功したところでDMARCに進みます。
DMARCの説明は、Google Workspaceのヘルプがわかりやすかったです。
DMARC を使用してなりすましと迷惑メールを防止する(support.google.com)
DMARCにはレポートとポリシーがありますが、ここではポリシーを扱います。

DMARCタグの理解

RCF7489 6.3 General Record Formatで、DNSに登録できるDMARCのレコードをみていきます。

タグの種類 内容
v DMARC1
p ポリシー。DMARCの検証に失敗した場合ドメインの所有者がメール受信者に対し、noneは特定のアクションなし、quarantineは疑わしいものとして処理、rejectは拒否することを望む、3種類が設定可能。
sp サブドメインのポリシー。pと同じようにサブドメインについて要求するもの。なければサブドメインはpに従う。
rua フィードバック送信先アドレス。
ruf DMARCの失敗レポートを送信するためのアドレス。これを記載することは、失敗したレポートを送ることを要求していることを示す。
adkim ドメイン所有者がDKIMをどう扱うかを示す。デフォルトはrで、RelaxMode、sは厳格(strict)モード。
aspf ドメイン所有者がSPFをどう扱うかを示す。デフォルトはrで、RelaxMode、sは厳格(strict)モード。
ri 集約レポート間で要求される間隔。デフォルトは86400(秒)。日時レポートを提出できなければならず、要求がある場合は1時間ごとのレポートを提出できなければならない。ただし日時以外はベストエフォートで良い。
fo 障害レポートオプション。レポートを作成する条件を示す。0はすべてのDMARCの認証でPass出なかった場合、1はPass以外がでた場合、dはDKIMで失敗した場合、sはSPFで失敗した場合。、rufタグが指定されていない場合は無視する。
rf メッセージ固有の障害レポートに使用される形式。デフォルトはafrf
pct パーセンテージ。DMARCポリシーが適用される0から100まで。デフォルトは100だが、ドメイン所有者が徐々に導入したい場合に利用する。ポリシーが適用されない場合、ポリシータグのより緩いものを適用する。rejectの場合はquarantine、quarantineの場合はnone。

今回はDMARCの成功失敗をさせることで、ポリシーの仕組みを確認します。
レポートの受信は行いません。

DMARCハンズオン

DNSにDMARCを登録します。
DMARCのレポートは受け取らないので、rua、rufは設定しません。

$ dig _dmarc.example.com TXT +short | grep DMARC
"v=DMARC1; p=reject; pct=100; adkim=s; aspf=s;"

上記のように応答するようレコードを登録したら、DKIMと同様にしてメールを送信してみます。
署名はOpenDKIMに任せます。

SPF、DKIM、DMARCともにPassしました。

dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=example.com

disが何を指しているかわかりませんが、DMARCは通ったようです。

次はDKIMに失敗する(dkim=neutralとなる)ようにして送ってみます。
/etc/opendkim.confのModeをv(Verifyのみ)に変更してsystemctl restart opendkimします。
先の失敗した手動の署名をDATAに入力して送ってみます。

dkim=neutral (body hash did not verify) header.i=@example.com header.s=sample header.b=xxxxxxx

GmailではDKIMはNeutralのためFAILになっていますが、
DMARCのほうはPassしました。

SPFとDKIM両方をパスしていないとDMARCはパスしないのではないか、と思い理由を確認します。

RFC7489 6.6.2. Determine Handling Policyに評価の流れが記載されています。
#3のDKIMのみ失敗していると考えられますが、#1 Fromからドメインの抽出、#2 DMARCのDNSレコード取得、#4 SPF Check、#5 Identifier Alignment Checkは通っていると考えられます。
最後の#5 Identifier Alignmentを見ていきます。

5.(前略)
If one or more of the Authenticated Identifiers align with the RFC5322.From domain, the message is considered to pass the DMARC mechanism check.
(後略)

RFC5322でいうFromで、認証済み識別子が1つ以ある場合はDMARCをパスしたとする、とあります。
RFC5322でいうFromとは、RFC5321でいうSMTPコマンドのMAIL FROMではなく、本文中、SMTPコマンドでいうDATAに含まれるFromを指します。

Identifier AlignmentはRFC7489 3.1 Identifier Alignmentに記載があります。

DMARC authenticates use of the RFC5322.From domain by requiring that it match (be aligned with) an Authenticated Identifier.

RFC7489 4.1 Authentication Mechanismsに、DKIMとSPFの2つがサポートされている、とあります。

今回SPFがパスしつつ、SPFの認証で使われたドメインとFromに記載されていたドメインが一致していたため、DMARCがパスしていると考えられます。
DMARCはSPF/DKIMのどちらかがパスし、かつパスしたドメインがFromと一致しているケースが1つ以上あれば、どちらかがFAILでもPassするような仕様でした。

次は、MAIL FROMとFromのドメインを異なるようにして行います。(MAIL FROMをcom、Fromをjp)
Fromを.jpドメインにするため、_dmarc.example.jpに新しくDMARCを設定しておきます。

$ dig _dmarc.example.jp TXT +short | grep DMARC
"v=DMARC1; p=reject; pct=100; adkim=s; aspf=s;"

DMARCのポリシーはp=rejectです。

実際に送ってみます。

postfix/smtp[765225]: 2847C63794: to=<postmaster@gmail.com>,
relay=gmail-smtp-in.l.google.com[108.177.97.26]:25,
delay=18, delays=17/0.04/0.96/0.55,
dsn=5.7.26, status=bounced (host gmail-smtp-in.l.google.com[108.177.97.26] said:
550-5.7.26 Unauthenticated email from example.jp is not accepted due to 550-5.7.26 domain's DMARC policy.
Please contact the administrator of 550-5.7.26 example.jp domain if this was a legitimate mail.
Please visit 550-5.7.26  https://support.google.com/mail/answer/2451690 to learn about the 550 5.7.26 DMARC initiative.
jh22-20020a170903329600b00153b2d16541si7893864plb.329 - gsmtp (in reply to end of DATA command))

送ろうとしたところ、Googleのメールサーバー側から拒否されてしまいました。
example.jpに登録されたDMARCのポリシーp=rejectで拒否されているようです。
DMARCは、SPFの.comではなくFromのドメインに書かれた.jpドメインのDMARCのレコードを見ているようです。
メールがrejectされないよう、.jpのレコードをp=noneに変更します。

今度はメールが送られるようになりましたが、受信側のGmailの表記でDMARCがFAILとなりました。

smtp.mailfrom=postmaster@example.com;
    dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=example.jp

Authentication-Results: mx.google.com;
    spf=pass (google.com: best guess record for domain of example.com@(hostname) designates xxx.xxx.xxx.xxx)

SPFはパスしていますが、DMARCの失敗の表記で、SPFの認証で使われているMAIL FROMのドメインと、DATAで使われているFromを比較した結果であるようなのがわかります。

次はSPFを失敗させ、DKIMが通るパターンを試します。DMARCのポリシーもrejectに戻しておきます。

$ dig example.com txt | grep spf
"v=spf1 -all"
$ dig _dmarc.example.com txt +short
"v=DMARC1; p=reject; pct=100; adkim=s; aspf=s;"

OpenDKIMを利用できるようにして送ってみます。

Authentication-Results: mx.google.com;
    dkim=pass header.i=@example.com header.s=sample header.b=xxxxxxx;
    spf=fail (google.com: domain of postmaster@example.com does not designate xxx.xxx.xxx.xxx as permitted sender)

smtp.mailfrom=postmaster@example.com;
    dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=example.com)

DKIMがパスしているので、SPFにFAILしていてもDMARCがパスしていることがわかります。

最後に、SPFが失敗、DKIMも失敗する場合(いわゆる迷惑メール)を試しておきます。
先のテストでDMARCのポリシーをnoneにしておきます。
DKIMは手動で失敗するものを使います。

 to=<postmaster@gmail.com>,
 elay=gmail-smtp-in.l.google.com[108.177.97.27]:25,
 delay=36, delays=34/0.04/1.1/0.53, dsn=5.7.26,
 status=bounced (host gmail-smtp-in.l.google.com[108.177.97.27] said:
 550-5.7.26 This message fails to pass SPF checks for an SPF record with a hard 550-5.7.26 fail policy (-all).
 To best protect our users from spam and 550-5.7.26 phishing,
 the message has been blocked. Please visit 550-5.7.26  https://support.google.com/mail/answer/81126#authentication for more 550 5.7.26 information.
 i25-20020a631319000000b00412b0b46a1csi9720048pgl.172 - gsmtp (in reply to end of DATA command))

SPFが通らない(-allにしている)ため、ブロックされてしまいました。
SPFを~allにして再度送ってみます。

Authentication-Results: mx.google.com;
    dkim=neutral (body hash did not verify) header.i=@example.com header.s=sample header.b=xxxxxxx;
    spf=fail (google.com: domain of transitioning postmaster@example.com does not designate xxx.xxx.xxx.xxx as permitted sender)

smtp.mailfrom=postmaster@example.com;
    dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=example.com)

SPFもDKIMも失敗したので、DMARCも失敗していることがわかります。

ハンズオンは以上になります。
各ドメインのTXTレコード(SPF)、sample._domainkey.example.com(DKIM)、_dmarc.example.com(DMARC)を元に戻します。

最後に

SPFはMAIL FROMのドメインを信頼して確認元に、
DMARCはFromのドメインを信頼して確認元にしている点は、手を動かして初めて気づきました。

SPFだけではFromを騙れてしまいますが、SPFとFromのドメインを確認してくれるのがDMARCと考えると、なかなか偽装は難しいのではないかと思いました。
SPF+DMARCであればDNSレコードの追加だけで済むので、実装も簡単に感じました。
もしメールを送信しないドメインであれば無条件でSPFレコードにv=spf1 -allとDMARCに"v=DMARC1; p=reject; pct=100; adkim=s; aspf=s;"を、既にメールを送信しているシステムであれば正規のSPFに加えてDMARCに"v=DMARC1; p=reject; pct=0; adkim=s; aspf=s;"もしくは"v=DMARC1; p=quarantine; pct=0; adkim=s; aspf=s;"を入れて、徐々にDMARCを効かせていくのが良いのかなと思います。

DKIMの導入も、大きな変更点としてはメールサーバーにOpenDKIMを入れるだけで済むので、キーのローテーションなど運用が厳密でない小さなシステムであれば、ハードルはそこまで高くないと感じました。

DMARCは識別してくれる受信者でないと意味がありませんが、徐々に増えていくことでしょう。

14
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?