SPFとDKIMとDMARC
Goolge宛のメールが時折届かないことがあるのは知っていたのですが、迷惑メールフィルターか何かに引っかかっているんだろうと、気にせずにいました。しかし、最近その頻度があがったので重い腰を上げ対応することにしました。
メール認証
Googleでは、2022年11月からメール認証をするようになりました。
ランダムで認証を行い通過しないものに関しては、迷惑メールに分類されるか、メールが届きません。
認証に失敗した場合は、送信者には次のようなメッセージの不達通知が届きます。
認証はSPFと、DKIMによって行われます。
SPF(Sender Policy Framework)
SPFとは何かについてはリンク先のJPNICのページにありますが、メールの送信元が偽装されていないかチェックするためのしくみです。
メールは構造上Fromアドレスを自由に設定可能なためなりすましが容易です。
SPFはそのなりすましを防止するためのひとつの手段です。
SPFはDNSに設定し、どのネットワークから送信されるべきかを指定します。
メールを受信した受信サーバーは、DSN中のSPFレコードを確認して、差出人のメールサーバーのIPアドレスと照合します。ここで指定されていない差出人の場合は不正なメールとして拒否をします。
これは受信側のサーバー頼りのセキュリティです。また、なりすましメールの送信自体を防ぐものではありません。
SPFレコードの基本的な設定の仕方は次のようになります。
これはDNSのSPFレコードに設定するルールですが、SPFレコードに対応しないDNSサーバーの互換性のためにTXTレコードに同内容のレコードを持つのが慣例となっています。
テキストレコードはの中身は次のようにします。DNSマネージャーの形態を持つ場合は自動で付与してくれることもありますが、多くの場合TXTレコードの値は"で囲います。
たとば、過去にこの記事として取り上げたBIND9で設定するなら次のようになります。
最初のmarune205.jp.の部分は、BINDではownerと名付けられていますが、この記事内では「ホスト名」と呼びます。
このホスト名は、もしメールがサブドメインから送られる場合はサブドメインを指定します。
続いいてテキストの部分を解説していきます。
v=spf1はバージョンを意味して最初に記述します。
バージョンの後は、判定条件をスペースで区切って記述していきます。
先の例では、ip4:としてIPv4アドレス範囲で192.168.1.0ネットワークからの送信を許可しています。
その後、192.168.2.254のIPアドレスを許可しています。
その後の-allですが、-は承認しないという意味です。allは文字通りすべてのアドレスを表します。
左からチェックしていき、最初に一致した条件の結果が返る仕組みになっていますので、全体として192.168.1.0/24でも192.168.2.254でもなければ、認証しないという意味になります。
IPv6で指定したい場合は、ip6:、ドメインで指定したい場合はa:を使います。ドメインで指定した場合は、受信側でDNSの問い合わせをして得られたIPアドレスと送信者のIPアドレスを照合します。
-はall以外でも使えます。また-のかわりに~を使うと正当なアドレスであっても認証に失敗する可能性があることを示します。
この-や~は「限定子」と呼ばれます。先のふたつは拒否を意味しますが、他に許可を意味する+があります。先の記述ではip4:やa:としましたが、これらは+ip4:や+a:省略記法です。
くれぐれも、エラー回避のためだけに+allとしないようにしてください。迷惑メールの踏み台になります。
限定子に応じて、認証の結果が分かれますが、この結果は「Pass(成功)、Fail(失敗)」の他に「SoftFail(失敗だけれども失敗の判定が間違いの可能性がある)とNutral(評価しない)」があります。SoftFailはさきほどの~による拒否を示し、Nutralは条件のいずれも一致しなかった場合か?限定子を付けた条件に一致した場合に返されます。
他、詳しい記述方法はIA japan:SPFのページにあります。
DKIM(DomainKeys Identified Mail)
DKIMは電子署名を利用してメールの本文やヘッダが改ざんされていないかをチェックする仕組みです。
SPFで送信元の正当性をチェックすることができますが、中身の改ざんは検知できません。そこでDKIMにより中身の改ざんのチェックを行います。
一方のDKIMは、電子署名により送信者や内容に変更がないことを保証できますが、メールがどこから送信されたか(送信元のIPアドレス)はチェックすることができません。そのためSPFと組み合わせて用いられます。
DKIMを運用するには、メールに署名する機構の設定と、署名を検証するための公開鍵をDNSに登録するふたつの作業が必要です。
署名に関してはメールサーバーで行うようにすれば管理が楽ですが、必ずしもメールサーバーで行わなければならないというものではありません。
またDNSへの登録はTXTレコードに設定します。
メールを受信したサーバーは、メールに添付されている署名とDNSのDKIM用のレコード(公開鍵)を使ってメールの改ざんを検知します。
PHPMailerでDKIM
DKIMの運用をしようとしたとき、メールサーバーがpostfilxなら、milterとしてopendkimを設定する方法がありますが、サーバー側でそのようなDKIMの設定ができないなら、本来のサーバーとの間にリレーサーバーを経由させたり、接続クライアント側で行うこともできます。以前使い方を紹介したPHPMailerを使えば簡単にDKIMの署名をすることができます。
まずは簡単なPHPMailerで署名をしてみます。
最初に、鍵のセットをもっていない場合は作成します。これはOpenSSLで作成が可能です。
秘密鍵を作成します。
秘密鍵と対になる公開鍵を作成します。
作成した秘密鍵を、PHPMailerに組み込みます。本番運用する鍵の場合は取り扱いに気を付けてください。
以前作成したPHPMailerのコードにDKIM用の設定を追加することで署名ができます。
sendmail.php
function php_sendmailWithDKIM($strTitle, $strMsg) {
mb_language("japanese");
mb_internal_encoding("UTF-8");
$mailer = new PHPMailer();
try {
//ログ出力と、保存の設定
$mailer->SMTPDebug =2; //SMTP::DEBUG_SERVER定数を利用するならSMTPクラスのインポートが必要です。
$mailer->Debugoutput = function($str, $level) { file_put_contents("phpmailer.log",$str); };
//DKIM設定
$mailer->DKIM_domain = 'ドメイン';
$mailer->DKIM_private = './dkim.key'; // 秘密鍵へのパス
$mailer->DKIM_selector = 'selector'; //DKIMセレクタ セレクタ._domankey.ドメインという形でDNSへ登録します。これにより複数の公開鍵の設定が可能になります
$mailer->DKIM_passphrase = '秘密鍵パスワード';
$mailer->DKIM_identity = "メールアドレス"; //通常は差出人のメールアドレスです。$mail->Fromとする場合は値をセットした後に行います
$mailer->DKIM_copyHeaderFields = false; //z=認証対象ヘッダコピーを付与しない(デバッグ時はtrueにすると便利です)
//SMTP設定
$mailer->IsSMTP();
$mailer->Host = "メールサーバー";
$mailer->SMTPAuth = true;
$mailer->Username = "ユーザー名";
$mailer->Password = "パスワード";
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mailer->Port = 587;
$mailer->CharSet = 'utf-8';//メッセージのエンコードを指定
$mailer->setFrom("送信者アドレス", "送信者名");
$mailer->addAddress("受信者アドレス", "受信者名");
//本文
$mailer->isHTML(true);//HTML形式のメールに
$mailer->Subject = mb_convert_encoding($strTitle,"utf-8","auto");
$mailer->Body = mb_convert_encoding($strMsg,"utf-8","auto");
return $mailer->send();//送信実行
} catch(Exception $e) {
//エラー
makeLog($e->getMessage());
return false;
}
}
送信したメールに次のようなDKIM-Signatureヘッダが付与されます。
DKIM-Signature: v=1; d=設定したドメイン; s=設定したセレクタ; a=rsa-sha256; q=dns/txt; t=1674019520; c=relaxed/simple; h=Date:To:From:Subject:Message-ID:X-Mailer:MIME-Version:Content-Type;
DKIM利用時のDNSサーバーへの登録
DKIM利用時はDNSサーバーのTXTレコードにふたつの設定をします。ひとつは必須の公開鍵の設定です。
まず、ホスト名を
とします。間の_domainkeyの部分は固定値です。セレクタはPHPMailerに設定する値と同じにします。ドメインは自身のドメインです。
値は
とし、先のようにBINDで設定するなら
となります。
DNSサーバーの仕様で、公開鍵は入りきらない場合は分割して複数のレコードにします。
もうひとつの設定は、ADSPレコードといいます。主にDKIMによる署名がどのように行われているかを受信者に知らせるものです。設定をするのが丁寧だとおもいますが、gmail.comにも設定はありませんでした。
ホスト名を、_adsp._domainkey.ドメインを使って次のいずれかを設定します。
- dkim=all
ドメインから送信されるメールはすべて電子署名がつけられているという意味になります。
- dkim=unknown
電子署名がつけられていたり、つけられなかったりする場合はunknownを設定します。
- dkim=discardable
すべて電子署名がつけられいるのに加え、署名が確認できない場合は破棄すべきという意味になります。
今回はPHPMailerを使わない送信は署名されないので、unknownを設定します。BIND9なら次のようになります。
DKIMの設定に関してもIA japanのサイトに詳しい説明があります。
署名の検証
DKIM署名の検証は手動でも論理上は可能ですが、筆者は断念しました。ThunderbirdのDKIM Verifierのようなツールを使うと簡単に検証することができます。
また先ほどPHPMailerで設定した$mailer->DKIM_domainがメールアドレスのドメインと違う場合は、$mailer->DKIM_identityの部分をコメントアウトすれば、署名管理の部分だけ違うドメインで管理することも可能です。(警告はでます)
OpenDKIMで、PostfixをDKIM署名サーバーに
PHPMailerと比べて少し設定の手間はかかりますが、Postfixを中継用にローカルで稼働させて、DKIM署名サーバーとして利用する事も可能です。
以前Postfixの設定でも紹介した、メール転送(Relay)設定とOpenDKIMを組み合わせることで対応します。
Postfixの転送設定は終えているものとして、OpenDKIMのインストールから始めます。
もし転送用のpostfixを新規にインストールするなら「サテライトシステム」として設定すると早いと思います。
# apt install opendkim opendkim-tools
インストールが終わったら、DKIM署名用の鍵を生成します。
# opendkim-genkey -b 2048 -d [ドメイン] -s [セレクタ] -D /etc/dkimkeys
bオプションに鍵長、dでドメイン、sでセレクタ、-Dで鍵の保存場所を指定します。
指定した保存場所に、「 セレクタ名.private 」と「 セレクタ名.txt 」ファイルが生成されます。
privateの方が署名用の秘密鍵、txtは公開鍵がDNS登録用の書式で入っています。
openDKIMの設定
OpenDKIMの設定ファイルは /etc/opendkim.conf となっています。デフォルトで有効になっているものを含め、今回は全体で次のような設定に修正しました。
opendkim.conf
Syslog yes
SyslogSuccess yes
# 否定応答時にログにその理由を出力します
# LogWhy no
Canonicalization relaxed/simple
OversignHeaders From
Domain [ドメイン]
Selector [セレクタ]
KeyFile [作成した秘密鍵へのパス]
UserId opendkim
UMask 007
Socket inet:8891@localhost
PidFile /run/opendkim/opendkim.pid
InternalHosts 172.16.1.0/24
- Syslog,SyslogSuccess
Syslog、SyslogSuccessはsyslogの設定です。
- Canonicalization
Canonicalizationでは正規化の方法を指定します。/で区切義られている場合は、前側がヘッダの正規化手段、後ろ側が本文の正規化手段です。
- OversignHeaders
OversignHeadersに関してはあとのSignHeadersと一緒に説明します。
- UserId,UMask
OpenDKIMを稼働させるUserIDです。Debianではopendkimとして稼働します。その際に求められるUMASK値は007です。
postfix(などのMTA)がOpenDKIMのsocketにアクセスするには、そのユーザーを(postfix)を opendkim グループに所属させる必要があります。ただし、これらの設定はunixソケットを利用する場合のもので、今回はmilterをinetで指定しているのでそれらの設定は不要です。
- Socket
ここではローカルホストの8891番ポートに設定します。
デフォルトでunix socket(local:/run/opendkim.sock等) に設定されていることもありますので、postfixのmilter設定に合わせて記述内容を調整します。
- PidFile
PidFileのパスです。デフォルトのままでOKです。
- InternalHosts
メールを署名する必要のある内部のホストを指定します。svモードの際はこれに含まれていないホストのメールの場合はv(検証)モードとなります。
次のように、IP、ネットワーク、ドメイン、ワイルドカードを使って記述できます。
192.168.1.1 172.16.1.0/24 marune205.net *.domain.jp
また、,(コンマ)を使って複数の設定をまとめることができます("172.16.0.0.16, 192.168.1.0/24"など)。
ここに載せていない設定群の中で、比較的利用しそうなものには次のようなものがあります。
- Mode
MODEは s が署名、v が検証となっています。デフォルトはsvですが、今回は署名だけなので s だけで稼働させてもいいと思います。
- SubDomains
SubDomains は文字通りサブドメインを対象とするか否かです。デフォルトはnoとなっていてはDomainに指定したドメインから派生しているサブドメインのメールにしか署名をしません。
- SignHeaders
SignHeaders は署名対象に含めるヘッダです。省略すると規定で必要とされているものとなります。複数ある場合は,で区切ります(例: Date,From,To)。必要な物が記述されていない場合や省略された場合は暗黙に追加されます。
前述の OversignHeaders との違いですが、SignHeadersの設定の場合はここで設定されているヘッダが存在しない場合は署名の対象としませんが、OversignHeadersの場合は存在しない場合もないものとして含めます。これを設定することで後からの重要なヘッダに対しての新規挿入の詐称を防ぐことができます。
- KeyTable
先の例ではDomainや署名用のキーやセレクタが単一の設定でしたが、状況によって使い分ける場合は、それらの設定をコメントアウトしてかわりに、KeyTableにファイルパスを指定して、その中で利用するキーの振り分けをします。
キーテーブルの書式は各行次のようになっています。少し紛らわしいですが前半は識別子としての文字列です。この文字列は次で説明するSigningTableで決定されます。後半はDKIMのエントリーとしてメールに付与されるドメインとセレクタ、署名に使う秘密鍵の指定となります。
[セレクタ]._domainkey.[ドメイン] [ドメイン]:[セレクタ]:[秘密鍵へのパス] - SigningTable
こちらは、メールアドレスに対して、キーの識別子を設定するものとなります
ファイルの書式は、次のようになっています。
[メールアドレス] [セレクタ]._domainkey.[ドメイン]メールアドレスを指定する事になっていますが、通常はドメイン単位だと思いますので次のような感じになります。
domain.jp default._domankey.domain.jp検索は、メールのFrom部に対して行われます。フル(host@domain.jp)で見つからない場合は、ホスト(domain.jp)での検索に切り替えられます。
また .domain.jpとしておくと、サブドメインを含めての検索になり、この場合は、一番細かいドメインから大きな方に向けての検索します。
ワイルドカードを使いたい場合は、opendkim.conf の signingTable の項目に refileを付けます(signingTable refile:/path/to/file) 。re=正規表現という意味ですが、ワイルドカードは*です(.*ではありません)
*@other.jp default._domankey.domain.jpメール内のFromと署名ドメインの異なる場合もこれらの設定を使えば署名できます。その際は "OversignHeaders From"の部分はコメントアウトする必要があります。
前述しましたが、異なるドメインでの署名がDKIMとして有効かどうかは受信するサーバーに依存すると思います。執筆時点ではGMailでも有効の判定を得られました。
ここで結びつける値は、[セレクタ]._domainkey.[ドメイン]の形になっていますが、これはKeyTableでの結び付けにのみ用いられ、実際に署名するセレクタやドメインはKeyTableで設定している値(後半部)が採用されます。
他にも様々な設定がありますが、詳しくはopendkim.confのmanページを参照してください。
次に、OpenDKIMをPostfixのmilterとして利用するようにPostfixの設定をします。設定はmain.cfに次の設定を追加します。
- smtpd_milters
smtpd_milters = inet:127.0.0.1:8891
postfixにメールが到着した場合のmilter(Mail Filter)の設定です。スペースやカンマ(,)で区切って複数指定できます。
- non_smtpd_milters
non_smtpd_milters = inet:127.0.0.1:8891
postfix外のメールのmilterの設定です。
- milter_default_action
milter_default_action = accept
PostfixやOpenDKIMの設定に不備があったりしてmilterの結果が戻ってこない場合の挙動です。acceptを指定しておくとそのような場合、milterの設定がないものとして取り扱います。rejectにするとエラー時にはメールを送信しません。
- milter_protocol
milter_protocol = 6
milterのプロトコルバージョンです。デフォルトで 6 となっていますが、念のため明示して設定しています。
あとは前述のようにtxtファイルの中身を確認して、前述と同じようにDNS設定をします。
ここまでの設定ができたら、PostfixとOpenDKIMを再起動します。
Postfixにメールを送信して署名がされれば成功です。
ついでにinetではなくsocketを使う方法も記しておきます。おそらくpostfixはデフォルトでchroot環境になっていますので、それに合わせて設定します。chrootのルートが/var/spool/postfixとなっている前提で話を進めます。
まず、OpenDKIMのsocketファイルを生成する場所をchroot内に作成します。この所有権はopendkimとします。
#mkdir -m o-rwx /var/spool/postfix/opendkim
#chown opendkim: /var/spool/postfix/opendkim
opendkim.confファイルにunixソケットの出力場所を記述します。既存のinetの設定はコメントアウトします。
opendkim.conf
Socket local:/var/spool/postfix/opendkim/opendkim.sockopendkimが所有しているソケットファイルにpostfixがアタッチできるように、ユーザー「 postfix 」を「 opendkim 」グループに追加します。
#adduser postfix opendkim
milterの設定をunixソケットにします。これはchroot内のルートからの指定になります。その点だけ間違わなければ絶対パスでも指定できますが、相対パスの方が誤解が少ないと思います。
opendkim.conf
smtpd_milters = unix:opendkim/opendkim.sock
non_smtpd_milters = unix:opendkim/opendkim.sock
ちなみに、postfixの設定ファイル main.cf の情報を man コマンドで表示したいと思った時、man postconf とするとセクション番号 1「postconf(1)」が表示されてしまいますが、目的のセクション番号は5なので、「 man 5 postconf 」とすることで、main.cf ファイルの man ページを開くことができます。
またセクション番号は、次のように採番されているそうです。
- 1:一般ユーザー向けのコマンド
- 2:システムコール
- 3:C言語のライブラリ関数
- 4:デバイスドライバ
- 5:設定ファイルや構成ファイル
- 6:ゲームやその他の娯楽用のコマンド
- 7:概要や概念に関する情報
- 8:システム管理者向けのコマンド
DMARC
DMARCはDomain-based Message Authentication, Reporting and Conformanceの略です。
SPFやDKIMはともに受信サーバーがチェックをするため、異常を検知したメールの取り扱いをどうするかは受信者にゆだねる形になっています。
DMARCは、SPFやDKIM認証の結果の取扱いをドメイン所有者が指定できるのに加え、受信者側のメールの認証結果をレポートとして受け取ることができるものです。
何度も紹介しているIA japanが発行している「DMARCによる新しいメール認証と導入の留意点」によればこの設定もDNSに行います。
DMARCにおけるDNS設定は次のようなります。
vの値はバージョンです。次のpはポリシーです。これは先ほどのDKIMの_adspレコードに似ていますが、異常を検知した場合どのように取り扱うべきかを指定します。
- none
なにもしない
- quarantine
隔離(迷惑メールとして格納)
- reject
拒否
ruaにはmailto:というプレフィックスを付けて、集約レポートの送信先であるメールアドレスを設定します。
rufとすると集約レポートではなく、失敗レポートの指定となります。ruaとrufの両方を設定することも可能です。
また先の例には載っていませんが、pct=の値を設定すると、異常を検知したメールに対してポリシーを適用する割合(%)を決められます。たとえばpct=10なら、異常を検知メールのうち10%に対してポリシーで設定した処理をするように指示をします。
これらの設定も受信サーバー頼りのものになりますので、拒否すべきメールが拒否されなかったり、レポートが来なかったりすることを承知しておかなければいけません。
spとするとサブドメインのポリシーを設定できます。基本的にサブドメインのポリシーが設定していない場合は、ドメインのポリシー(p)の値が採用されます。
spの使い方は、GoogleのDMARCの値を例にとって説明します。この記事を執筆した時点のGoogleにおけるDMARCレコードは
となっていました。
これはxxx@gmail.comから送信されたメールはSPFやDKIMの認証に通過しないメールに関してはGoogleは何も指示をだしておらず、受信者にゆだねられている状態です。ただ、xxx@fake.gmail.comやxxx@dummy.gmail.comといったサブドメインから送られたメールは(実際には存在しないはずなので)隔離するように案内している状況です。
DMARCのレポートを利用するだけなら先のDNSレコードの設定だけで済みますが、DMARCに完全に対応するにはメールサーバー側にレポートを送信する機構を付与しなければいけません。そのようなDMARCの実装には、opendmarcライブラリがあります。