|||||||||||||||||||||

なんぶ電子

- 更新: 

SPFとDKIMとDMARC

DKIM

Goolge宛のメールが時折届かないことがあるのは知っていたのですが、迷惑メールフィルターか何かに引っかかっているんだろうと、気にせずにいました。しかし、最近その頻度があがったので重い腰を上げ対応することにしました。

メール認証

Googleでは、2022年11月からメール認証をするようになりました。

ランダムで認証を行い通過しないものに関しては、迷惑メールに分類されるか、メールが届きません。

認証に失敗した場合は、送信者には次のようなメッセージの不達通知が届きます。

This message does not pass authentication checks (SPF and DKIM both do not pass). SPF check for [メールドメイン] does not pass with ip:[IPアドレス].To best protect our users from spam, the message has been blocked.

認証はSPFと、DKIMによって行われます。

SPF(Sender Policy Framework)

SPFとは何かについてはリンク先のJPNICのページにありますが、メールの送信元が偽装されていないかチェックするためのしくみです。

メールは構造上Fromアドレスを自由に設定可能なためなりすましが容易です。

SPFはそのなりすましを防止するためのひとつの手段です。

SPFはDNSに設定し、どのネットワークから送信されるべきかを指定します。

メールを受信した受信サーバーは、DSN中のSPFレコードを確認して、差出人のメールサーバーのIPアドレスと照合します。ここで指定されていない差出人の場合は不正なメールとして拒否をします。

これは受信側のサーバー頼りのセキュリティです。また、なりすましメールの送信自体を防ぐものではありません。

SPFレコードの基本的な設定の仕方は次のようになります。

これはDNSのSPFレコードに設定するルールですが、SPFレコードに対応しないDNSサーバーの互換性のためにTXTレコードに同内容のレコードを持つのが慣例となっています。

テキストレコードはの中身は次のようにします。DNSマネージャーの形態を持つ場合は自動で付与してくれることもありますが、多くの場合TXTレコードの値は"で囲います。

"v=spf1 ip4:192.168.1.0/24 ip4:192.168.2.254 -all"

たとば、過去にこの記事として取り上げたBIND9で設定するなら次のようになります。

marune205.jp. 3600 IN TXT "v=spf1 ip4:192.168.1.0/24 ip4:192.168.2.254 -all"

最初の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で作成が可能です。

秘密鍵を作成します。

openssl genrsa -aes256 -out dkim.key 2048

秘密鍵と対になる公開鍵を作成します。

openssl rsa -in dkim.key -pubout -out dkim.pub

作成した秘密鍵を、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.ドメイン

とします。間の_domainkeyの部分は固定値です。セレクタはPHPMailerに設定する値と同じにします。ドメインは自身のドメインです。

値は

"v=DKIM1; k=rsa; p=公開鍵の中身"

とし、先のようにBINDで設定するなら

セレクタ._domainkey.marune205.jp. 3600 IN TXT "v=DKIM1; k=rsa; p=公開鍵の中身"

となります。

DNSサーバーの仕様で、公開鍵は入りきらない場合は分割して複数のレコードにします。

もうひとつの設定は、ADSPレコードといいます。主にDKIMによる署名がどのように行われているかを受信者に知らせるものです。設定をするのが丁寧だとおもいますが、gmail.comにも設定はありませんでした。

ホスト名を、_adsp._domainkey.ドメインを使って次のいずれかを設定します。

  • dkim=all

    ドメインから送信されるメールはすべて電子署名がつけられているという意味になります。

  • dkim=unknown

    電子署名がつけられていたり、つけられなかったりする場合はunknownを設定します。

  • dkim=discardable

    すべて電子署名がつけられいるのに加え、署名が確認できない場合は破棄すべきという意味になります。

今回はPHPMailerを使わない送信は署名されないので、unknownを設定します。BIND9なら次のようになります。

_adsp._domainkey.marune205.jp. 3600 IN TXT "dkim=unknown"

DKIMの設定に関してもIA japanのサイトに詳しい説明があります。

署名の検証

DKIM署名の検証は手動でも論理上は可能ですが、筆者は断念しました。ThunderbirdのDKIM Verifierのようなツールを使うと簡単に検証することができます。

また先ほどPHPMailerで設定した$mailer->DKIM_domainがメールアドレスのドメインと違う場合は、$mailer->DKIM_identityの部分をコメントアウトすれば、署名管理の部分だけ違うドメインで管理することも可能です。(警告はでます)

DKIM

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

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番ポートに設定します。

  • PidFile

    PidFileのパスです。デフォルトのままでOKです。

  • InternalHosts

    メールを署名する必要のある内部のホストを指定します。

    書式は次のようになります。

    192.168.1.1
    172.16.1.0/24
    marune205.net
    *.domain.jp

    また、複数行の指定を記述した同様の書式のファイルを作りそのパスを指定することもできます。

ここに載せていない設定群の中で、比較的利用しそうなものには次のようなものがあります。

  • Mode

    MODEは s が署名、v が検証となっています。デフォルトはsvですが、今回は署名だけなので s だけで稼働させてもいいと思います。

  • SubDomains

    SubDomains は文字通りサブドメインを対象とするか否かです。通常Domainに指定したドメインのメールにしか署名をしませんが、この値が yes ならDomainが含有するサブドメインにも署名をします。

  • SignHeaders

    SignHeaders は署名対象に含めるヘッダです。省略すると規定で必要とされているものとなります。複数ある場合は,で区切ります(例: Date,From,To)。必要な物が記述されていない場合や省略された場合は暗黙に追加されます。

    前述の OversignHeaders との違いですが、SignHeadersの設定の場合はここで設定されているヘッダが存在しない場合は署名の対象としませんが、OversignHeadersの場合は存在しない場合もないものとして含めます。これを設定することで後からの重要なヘッダに対しての新規挿入の詐称を防ぐことができます。

  • KeyTable,SigningTable

    先の例ではDomainや署名用のキー、セレクタが単一の設定でしたが状況によって使い分ける場合は、それらの設定をコメントアウトしてかわりに、KeyTable,SigningTableを使います。テーブルファイルへのパスを記述しファイルに実際のルールを記載します。

他にも様々な設定がありますが、詳しくは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.sock

opendkimが所有しているソケットファイルに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設定は次のようなります。

_dmarc.marune205.jp. 3600 IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-feedback@marune205.jp"

vの値はバージョンです。次のpはポリシーです。これは先ほどのDKIMの_adspレコードに似ていますが、異常を検知した場合どのように取り扱うべきかを指定します。

  • none

    なにもしない

  • quarantine

    隔離(迷惑メールとして格納)

  • reject

    拒否

ruaにはmailto:というプレフィックスを付けて、集約レポートの送信先であるメールアドレスを設定します。

rufとすると集約レポートではなく、失敗レポートの指定となります。ruaとrufの両方を設定することも可能です。

また先の例には載っていませんが、pct=の値を設定すると、異常を検知したメールに対してポリシーを適用する割合(%)を決められます。たとえばpct=10なら、異常を検知メールのうち10%に対してポリシーで設定した処理をするように指示をします。

これらの設定も受信サーバー頼りのものになりますので、拒否すべきメールが拒否されなかったり、レポートが来なかったりすることを承知しておかなければいけません。

spとするとサブドメインのポリシーを設定できます。基本的にサブドメインのポリシーが設定していない場合は、ドメインのポリシー(p)の値が採用されます。

spの使い方は、GoogleのDMARCの値を例にとって説明します。この記事を執筆した時点のGoogleにおけるDMARCレコードは

"v=DMARC1; p=none; sp=quarantine; rua=mailto:mailauth-reports@google.com"

となっていました。

これはxxx@gmail.comから送信されたメールはSPFやDKIMの認証に通過しないメールに関してはGoogleは何も指示をだしておらず、受信者にゆだねられている状態です。ただ、xxx@fake.gmail.comやxxx@dummy.gmail.comといったサブドメインから送られたメールは(実際には存在しないはずなので)隔離するように案内している状況です。

DMARCのレポートを利用するだけなら先のDNSレコードの設定だけで済みますが、DMARCに完全に対応するにはメールサーバー側にレポートを送信する機構を付与しなければいけません。そのようなDMARCの実装には、opendmarcライブラリがあります。

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告