ここでは、DovecotとPostfixで設定したメールサーバをTLSに対応させましょう。FreeBSD 14.1で動作確認していますが、設定ファイルのあるディレクトリを読み替えれば、Linuxでも設定できると思います。

方針

TLSには、「暗黙的(Implicit)なTLS」と「明示的(Explicit)なTLS」の2種類があります。違いは

  • Implicit TLSは、接続した時点からTLSで暗号化します。特別な手順を踏むことはありません。WebサーバのTLS化(HTTPS)はこの方式です。
  • Explicit TLSは、最初は平文で接続し、「STARTTLS」コマンドを送るとTLSの暗号化が始まります。

IMAPにもSMTPにもSubmissionにも、それぞれImplicit TLSとExplicit TLSが存在します。Explicit TLSはSTARTTLSコマンドがなければ平文のままで通信しますのでポート番号は平文のものと共有できますが、Impicit TLSは最初からTLSの暗号化が始まりますので平文のプロトコルとポートを分ける必要があります。

ポート番号を列挙しますと

  • IMAP
    • Explicit TLS ポート番号 TCP/143
    • Implicit TLS ポート番号 TCP/993(IMAPS)
  • POP3
    • Explicit TLS ポート番号 TCP/110
    • Implicit TLS ポート番号 TCP/995(POP3S)
  • SMTP(認証を必須化できない、サーバ間のメール転送)
    • Explicit TLS ポート番号 TCP/25
  • Submission(認証を必須化できる、サーバとユーザサイドのメール転送)
    • Explicit TLS ポート番号 TCP/587
    • Implicit TLS ポート番号 TCP/465(Submissions)

では、「IMAPとPOP3とSubmissionでは、最初からImplicit TLSだけでいいじゃないか」と思うかもしれませんが、クライアントソフトがSTARTTLSには対応しているがImplicit TLSには対応していない、という可能性も考えられます(可能性は低いですが…)。

更に話をややこやしくしているのが、SubmissionのImpicit TLSのポート番号であるTCP/465は他のプロトコルにも割り当てられている、ということです。「広く使われているが使わないほうがいい」と断言している情報も散見されます。

そこで、今回の設定では「IMAPとPOP3とSubmissionではSTARTTLSを必須化、その他にIMAPSとPOP3Sにも対応、サーバ間の転送では、送信するときには相手が対応していればSTARTTLSで通信、受信するときにはSTARTTLS案内して相手に任せる、SubmissionのImplicit TLS(TCP/465)には非対応」という方針で行きます。

証明書の用意

サーバをTLS化するときには、当然ですがTLS証明書が必要です。内部で使う分にはいわゆる「オレオレ証明書」でいいと思いますが(ThunderbirdとかOutlookのようなメーラーは警告を出すので、黙らせるようにしなければなりませんが…)、対外的に公開する際には正式なTLS証明書を取得しましょう。Let's Encryptから証明書を取得すれば無料です。

サーバが複数のホスト名で接続されるとき(DNSでCNAMEを設定しているなど)は、接続されるホスト名に対応する証明書が必要です。DovecotもPostfixもSNI(接続の際に用いられたホスト名に応じてTLS証明書を使い分ける)に対応しています。

Dovecotの設定

設定ファイルは「/usr/local/etc/dovecot/conf.d/10-ssl.conf」になります。このファイルの

ssl = no

ssl = required

に書き換えます。この設定で、「IMAPSとPOP3Sを有効化、IMAPとPOP3ではSTARTTLSを強制(平文では認証を通さない)」を一気に指定できます。

証明書が一つの場合(SNIを使わない場合)

同じく「/usr/local/etc/dovecot/conf.d/10-ssl.conf」にある

ssl_cert = </etc/ssl/certs/dovecot.pem
ssl_key = </etc/ssl/private/dovecot.pem

を書き換え(コメントアウトしてあったら先頭の「#」を削除し)、実際の証明書ファイルと鍵ファイルへのパスを書きます。Let's Encryptで取得した場合、証明書ファイルは「/usr/local/etc/letsencrypt/live/ホスト名/fullchain.pem」、鍵ファイルは「/usr/local/etc/letsencrypt/live/ホスト名/privkey.pem」になります。注意しなければならないのは、証明書ファイル・鍵ファイルへのパスを書くときに「<」を付けることです。デフォルの設定例にも付いています。

もし、鍵ファイルにパスワードが設定されている場合は、

ssl_key_password = password

とその鍵ファイルのパスワードを指定します。

証明書が複数の場合

SNIを使います。まずは、デフォルトで使われる証明書ファイル・鍵ファイルを

ssl_cert = </etc/ssl/certs/dovecot.pem
ssl_key = </etc/ssl/private/dovecot.pem

に書き込みます。ここで指定された証明書ファイル・鍵ファイルで対応できないホスト名に対しては、この設定ファイルに

localhost hostname.example.com {
  ssl_cert = </path/to/cert1.pem
  ssl_key = </path/to/key1.pem
}

localhost hostname.example.net {
  ssl_cert = </path/to/cert2.pem
  ssl_key = </path/to/key2.pem
}
...

と必要なだけ「localhost ホスト名 { ... }」を書き加えます。

鍵ファイルにパスワードが設定されている場合は、対応する箇所に

ssl_key_password = password

を付け加えます。

ところで、このサイトの記事では最初の設定のとき「TLSでない場合でもPLAIN認証を許可」という設定をしました。TLS対応の上にTLS強制となった今、この設定は不要です(不要というより、セキュリティ的に問題です)。元に戻してしまいましょう。設定ファイルは「/usr/local/etc/dovecot/conf.d/10-auth.conf」です。

disable_plaintext_auth = no

を元のコメントアウト化&デフォルトの「yes」に変更して

#disable_plaintext_auth = yes

にしてしまいます。

ここまで設定できたら

tail -f /var/log/maillog

でログを監視しながら

service dovecot reload

で設定ファイルを読み込ませます。もし、証明書ファイルや鍵ファイルのパスに間違えがあれば、ログにその旨エラーが出ます。

動作確認

証明書の読み込みに成功していれば、次は「ホスト名と証明書の対応が間違いないか」確認しましょう。今どきの証明書はホスト名情報がTLS証明書のSANにホスト名情報が入っていますので、これを確認します。次のコマンドを実行します。

openssl s_client -connect hostname.exmample.com:993 < /dev/null 2> /dev/null | openssl x509 -text | grep DNS

これでDovecotが提示したTLS証明書のホスト名情報が確認できますので、要求したホスト名と一致するかどうか確認します。SNIを使って複数のTLS証明書を読み込ませているなら、ホスト名ごとにこれを繰り返します。

ローカルで使っているサーバで「オレオレ証明書」を設定していて、その証明書にSANを設定していない場合は

openssl s_client -connect hostname.example.com:993 < /dev/null 2> /dev/null | openssl x509 -noout -subject

で確認します。

Postfixの設定

Postfixでは、「/usr/local/etc/postfix/main.cf」に設定を書き込みます。下記の内容を追記して下さい。

# SSL/TLS Settings
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_tls_cert_file = /path/to/certfile.pem
smtpd_tls_key_file = /path/to/keyfile.pem
smtpd_tls_session_cache_timeout = 0
smtpd_tls_received_header = yes

「smtpd_tls_cert_file」には、デフォルトの証明書ファイルの場所を書き込んで下さい。同様に、「smtpd_tls_key_file」には、デフォルトの鍵ファイルの場所を書き込んで下さい。

もし、サーバが複数の名前で接続されるのであれば(例えば、DNSのCNAMEで「hostname.example.com」が「mail.example.com」を指すように設定してあるなど)、それに対応してSNIの設定を書き加えます。「/usr/local/etc/postfix/main.cf」に次の設定を書き加えます。

# TLS SNI
tls_server_sni_maps = hash:/usr/local/etc/postfix/tls_server_sni_maps

この「tls_server_sni_maps」に指定されたファイル(今回は「/usr/local/etc/postfix/tls_server_sni_maps」)に、次のように

hostname.example.com /path/to/keyfile.pem /path/to/certfile.pem
mail.example.com /psth/to/keyfile_for_mail.pem /path/to/certfile_for_mail.pem
mail.example.net /path/to/keyfile_for_net.pem /path/to/certfile_for_net.pem

と必要なだけ「ホスト名(FQDN)」「鍵ファイルへのパス」「証明書ファイルへのパス」を1ホストにつき1行で書き込んでいきます。そうして出来上がったファイルはハッシュ化しなければなりませんので、次のコマンドを実行してハッシュ化します。

postmap -F /usr/local/etc/postfix/tls_server_sni_maps

ここで、「postmap」のオプションの「-F」を忘れないように注意して下さい。

ちなみに、PostfixはSNIファイルで指定されていないホスト名で接続されたときは「main.cf」ファイルで指定された証明書・鍵ファイルを使いますが、その時はsyslogに「デフォルトの証明書ファイルを使いました」と一々吐き出しますので、ログが肥大化します。そのため、SNIを使うときには参照されるホスト名すべてについて書き出すのが良いかと思います。

次に、SubmissionではSTARTTLSを強制します。設定するファイルは「/usr/local/etc/postfix/master.cf」になります。このファイルの

submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
#  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes

となっている箇所がありますので、「smtpd_tls_security_level」のコメントアウトを外し、

submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes

にしてしまいます。これで、SubmissionではSTARTTLSを強制しました。

ここまで設定したら、コマンドラインで

service postfix reload

を実行して、Postfixに設定ファイルを再読み込みさせます。エラーが無いかどうか、「/var/log/maillog」を確認して下さい。

動作確認

Dovecotの時とほぼ一緒ですが、Implicit TLSではなくExplicit TLSなので、一部コマンドが異なります。証明書にSANが設定されているときは、次のコマンドを実行します。

openssl s_client -connect hostname.example.com:25 -starttls smtp < /dev/null 2> /dev/null | openssl x509 -text | grep DNS

コマンドラインで指定したホスト名と、コマンドの結果得られるホスト名が一致することを確認して下さい。証明書にSANがない場合は、

openssl s_client -connect hostname.example.com:25 -starttls smtp < /dev/null 2> /dev/null | openssl x509 -noout -subject

になります。

SNIを設定している場合は、設定したホスト名全てでこれを繰り返します。

これでサーバ間の転送に用いられるTCP/25の確認ができましたので、次はSubmission(TCP/587)の動作確認をします。証明書にSANが設定されている場合は

openssl s_client -connect hostname.example.com:587 -starttls smtp < /dev/null 2> /dev/null | openssl x509 -text | grep DNS

SANが設定されていない場合は

openssl s_client -connect hostname.example.com:587 -starttls smtp < /dev/null 2> /dev/null | openssl x509 -noout -subject

で確認します。

これも、SNIを用いていれば、参照されるホスト名全てで上記のコマンドを繰り返します。

Thunderbirdでアクセスする場合

Thunderbirdでアクセスする場合、このままの設定だとアカウント作成時に「送信メールサーバのSTARTTLSを有効にできない」という現象が発生します。原因はThunderbird側にありバグ報告も出していますが、Thunderbird側でFixされるまでの間はサーバ側で対処するしかありません。この場合、次の設定を「/usr/local/etc/postfix/main.cf」に付け加えます。

# Thunderbird Workaround
# (This allows Thunderbird to recognize STARTTLS when create account)
smtpd_forbid_unauth_pipelining = no

付け加えたら

service postfix reload

で設定ファイル読み込み直してください。