🐕

メールサーバのSSL証明書をLet's Encryptで作る

に公開

20年くらい自宅サーバで運用しているメールがあり、自分一人しか利用者がいないため、これまではずっと自己署名証明書を使っていました。ところがiPhoneをiOS18にしたところ、メールアプリからそのIMAPサーバに接続できなくなりました。

自己署名証明書を「信頼」してもiPhoneのメールアプリで次のようなメッセージが表示されてしまうようになりました。

IMAPサーバはdovecotを使用していて、次のようなログが記録されています。

imap-login: Disconnected: Connection closed: SSL_accept() failed: error:
0A000416:SSL routines::sslv3 alert certificate unknown: SSL alert number
 46 (no auth attempts in 0 secs): user=<>, rip=**.**.**.**, lip=192.168.
0.4, TLS handshaking: SSL_accept() failed: error:0A000416:SSL routines::
sslv3 alert certificate unknown: SSL alert number 46, session=<3jJ2OjwpB
u3Zsjxc>

なお、Macの方も最新のSequoiaにしましたが、メールアプリからは引き続き問題なく接続できています。

自己署名証明書を作り直してみたりなどしても解決しなかったので、Let's Encryptで証明書を作ってみることにしました。結果を先に書くと、無事IMAPサーバに接続できるようになりました。

以下は、その際にやったことになります。同様の記事は他にも多数ありますが、前提条件が違うと微妙に手順が異なるところがあったので記録として残しておきます。

前提条件

  • OS: Debian bookworm
  • IMAPサーバ: dovecot(Debian bookwormのパッケージのもの)
  • SMTPサーバ: postfix(同上)
  • DNSサーバ: BIND(同上)
  • メールサーバのホスト名: mail.example.com
  • 作成する証明書: ワイルドカードではないもの
  • Let's Encryptのチャレンジの種類: DNS-01

対象のメールサーバは外部からのHTTP/HTTPS通信が届かないネットワーク構成のため、HTTP-01チャレンジは使用できません。そのためDNS-01チャレンジを使用します。DNS-01チャレンジを使用する場合、ワイルドカード証明書を作成することができるようですが、今回はワイルドカード証明書を必要としないので、ワイルドカードにはしません。

OSはDebian bookwormで、dovecotとpostfixは自己署名証明書での運用が既にできている前提です。そのため、dovecotとpostfixの設定変更は証明書の切り替えのみとなります。

certbotのインストール

# apt-get install certbot python3-certbot-dns-rfc2136

DNS-01チャレンジを使用するので、certbot 本体の他に python3-certbot-dns-rfc2136 が必要です。他に依存パッケージが40個くらいインストールされました。

ゾーンの追加

DNS-01チャレンジを使用するためには、DNSの動的更新(RFC2136)によってTXTレコードの追加/削除ができるようにする必要があります。この記事ではメールサーバのホスト名を mail.example.com としているので、_acme-challenge.mail.example.com という名前に対してDNSの動的更新によるTXTレコードの追加/削除が必要となります。動的更新で何かトラブルがあった時に example.com のゾーン全体に影響してしまうのを避けるため、ゾーンを分けます。

分けるゾーンは _acme-challenge.mail.example.com です(mail.example.com ではありません)。

_acme-challenge.mail.example.com のゾーンを分けるために、ゾーンファイルを次のように作成します。なお、example.com のネームサーバは仮に ns1.example.com とします。

$TTL 86400
@	IN	SOA	ns1.example.com. hostmaster.example.com. (
				2024122701 ; serial
				86400      ; refresh (1 day)
				3600       ; retry (1 hour)
				1209600    ; expire (2 weeks)
				1200       ; minimum (20 minutes)
				)
@	IN	NS	ns1.example.com.

このゾーンファイルは /var/cache/bind/_acme-challenge.mail.example.com.zone に保存します。

このゾーンを追加するために /etc/bind/named.conf を修正します。

// 追加
zone "_acme-challenge.mail.example.com" {
	type master;
	file "/var/cache/bind/_acme-challenge.mail.example.com.zone";
	check-names ignore;
	update-policy {
		// certbot-key はDNS動的更新用の鍵の名前(後述)
		grant certbot-key. name _acme-challenge.mail.example.com. TXT;
	};
};

_acme-challenge という文字列はドメイン名としては不正な名前のため、check-names ignore; の設定で名前のチェックを無効にする必要があります。

grant で始まる行の certbot-key は次に説明するDNS動的更新用の鍵の名前です。

DNS動的更新用の鍵の作成

tsig-keygen コマンドを使用します。

tsig-keygen -a アルゴリズム 鍵の名前

という引数を取ります。アルゴリズムは hmac-sha512 にしました。名前は、前述の certbot-key を指定します。

# tsig-keygen -a hmac-sha512 certbot-key

実行すると、標準出力に次のように出力されます。

key "certbot-key" {
	algorithm hmac-sha512;
	secret "AC9cpGlhJXW765kxS+NRV7IauQThXvI2NFNwwkJfhzUwOMukiBySR4jN4LqjDsJd7aorV/28NvJMHuvq5ChzZQ==";
};

これを /etc/bind/certbot-key.key に保存し、オーナーとグループを bind:bind、パーミッションを 600 にします。

この鍵をBINDで利用するために /etc/bind/named.conf に次の行を追加します。

include "/etc/bind/certbot-key.key";

設定変更を反映します。

# systemctl reload named

変更内容の確認をします。

# dig @DNSサーバのIPアドレス _acme-challenge.mail.example.com SOA

; <<>> DiG 9.10.6 <<>> @**.**.**.** _acme-challenge.mail.example.com SOA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33832
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;_acme-challenge.mail.example.com.	IN	SOA

;; ANSWER SECTION:
_acme-challenge.mail.example.com.	86400 IN SOA	ns1.example.com. hostmaster.example.com. 2024122701 86400 3600 1209600 1200

;; Query time: 19 msec
;; SERVER: **.**.**.**#53(**.**.**.**)
;; WHEN: Fri Dec 27 15:56:42 JST 2024
;; MSG SIZE  rcvd: 119

RFC2136クレデンシャルファイルの作成

certbotをDNS-01チャレンジで実行するときに必要になるファイルを作成します。

次の内容のファイルを作成し、/etc/letsencrypt/dns-rfc2136.ini に保存します。

dns_rfc2136_server = DNSサーバのIPアドレス
dns_rfc2136_port = 53
dns_rfc2136_name = certbot-key.
dns_rfc2136_secret = AC9cpGlhJXW765kxS+NRV7IauQThXvI2NFNwwkJfhzUwOMukiBySR4jN4LqjDsJd7aorV/28NvJMHuvq5ChzZQ==
dns_rfc2136_algorithm = HMAC-SHA512

dns_rfc2136_namedns_rfc2136_algorithm は前述の tsig-keygen で指定した名前とアルゴリズムです。dns_rfc2136_secret はそのときに生成された secret です。

certbotの実行

テスト実行

まず --dry-run を付けてテスト実行します。

# certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/dns-rfc2136.ini -d 'mail.example.com' --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Simulating a certificate request for mail.example.com
Waiting 60 seconds for DNS changes to propagate
The dry run was successful.

と出ればOKです。

Waiting 60 seconds for DNS changes to propagate

のところで60秒待ちますので、その隙に

# dig @DNSサーバのIPアドレス _acme-challenge.mail.example.com TXT

として、_acme-challenge.mail.example.com の TXT レコードが動的に設定されているか確認します。

; <<>> DiG 9.10.6 <<>> @**.**.**.** _acme-challenge.mail.example.com TXT
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59372
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;_acme-challenge.mail.example.com.	IN	TXT

;; ANSWER SECTION:
_acme-challenge.mail.example.com.	120 IN	TXT	"UxbZwwS7ShMQZy1E9_9sQ3wue75CTrz4MwOSiI8BBVk"

;; Query time: 18 msec
;; SERVER: **.**.**.**#53(**.**.**.**)
;; WHEN: Fri Dec 27 13:53:45 JST 2024
;; MSG SIZE  rcvd: 115

60秒過ぎるとTXTレコードは削除されます。

本番

--dry-run を外して本番を行います。

# certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/dns-rfc2136.ini -d 'mail.example.com'
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for mail.example.com
Waiting 60 seconds for DNS changes to propagate

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/mail.example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/mail.example.com/privkey.pem
This certificate expires on 2025-03-27.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Certbot has set up a scheduled task to automatically renew this certificate in the background. とメッセージが出ているので、更新処理はこのまま特に何もしなくても自動で行われるようです。

# systemctl status certbot.timer

とすると、更新処理のステータスがわかります。

* certbot.timer - Run certbot twice daily
     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; preset: enabled)
     Active: active (waiting) since Fri 2024-12-20 17:39:02 JST; 6 days ago
    Trigger: Fri 2024-12-27 19:41:18 JST; 5h 4min left
   Triggers: * certbot.service

dovecotとpostfixの設定

はじめに書いたように、証明書の切り替えのみを行います。

/etc/dovecot/conf.d/10-ssl.conf
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem
/etc/postfix/main.cf
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.example.com/privkey.pem

証明書の変更を反映します。

# systemctl reload postfix dovecot

まとめ

Debian bookworm で postfix と dovecot にLet's EncryptのSSL証明書(DNS-01チャレンジ)使うには、

  • certbotpython3-certbot-dns-rfc2136 のパッケージをインストール
  • DNSの動的更新(RFC2136)によってTXTレコードの追加/削除ができるようにする
    • ゾーンの追加
    • tsig-keygen で鍵の作成
  • certbot で使用するRFC2136クレデンシャルファイルの作成
  • certbot の実行
  • postfix と dovecot の証明書の設定

という手順になります。

株式会社ソニックムーブ

Discussion