2026年8月6日
2026年8月6日
DoH (DNS over HTTPS) と DANE を併用する設定|自ドメインの TLSA を DoH で外から点検し、証明書とのずれを監視する
DANE を公開しているドメインでは、TLSA レコードが外部の検証付きリゾルバーからどう見えているかを定期的に確かめる仕組みが欠かせません。DoH(DNS over HTTPS)は 443 番だけで公開リゾルバーに問い合わせられるため、この「外からの点検」に向いています。この記事では、受信メールサーバーに DANE を設定している運用者が、DoH を使って TLSA と DNSSEC の状態を確認し、実際の証明書とのずれを cron で検知するまでの手順を説明します。
なお、送信側のメールサーバー(Postfix など)の名前解決そのものを DoH 経由にする構成は別の話題です。この記事は「DANE を公開する側が、DoH を点検の道具として併用する」場面に絞っています。
DoH と DANE はそれぞれ何を守るのか
最初に役割を整理しておくと、設定の判断で迷いません。
DANE(SMTP では RFC 7672)は、受信メールサーバーの証明書や公開鍵の指紋を TLSA レコードとして DNS に載せ、送信側が TLS 接続時に照合する仕組みです。TLSA は DNSSEC で署名されていることが前提で、署名の検証に失敗した応答は信用されません。つまり DANE の安全性は「ゾーンの DNSSEC が正しく動いていること」と「TLSA の値が実際の鍵と一致していること」の二つで決まります。
DoH(RFC 8484)は、DNS の問い合わせと応答を HTTPS で運ぶ仕組みです。守るのは自分と DoH サーバーの間の通信路で、応答の中身が正しいかどうかは DoH 自体では保証されません。DoH サーバーが DNSSEC を検証するリゾルバーであれば、応答に AD ビット(検証済みの印)が付きます。
この二つを組み合わせると、次のことが分かります。
- 公開リゾルバーから見て、TLSA が DNSSEC の検証に通っているか(AD ビットの有無、SERVFAIL になっていないか)
- 公開されている TLSA の値が、MX が実際に提示している鍵と一致しているか
送信側の多くは、自分で検証するリゾルバーを使って TLSA を引きます。公開リゾルバーでの結果はその「代わりの目安」であり、すべての送信元の見え方を保証するものではない点は押さえておいてください。
前提として確認しておくこと
作業の前に、次の状態になっているかを確認します。
- ゾーンが DNSSEC で署名され、親ゾーンに DS レコードが登録されている
- MX ホスト名に対して
_25._tcp.MXホスト名の TLSA が公開されている - 点検するマシンに BIND 9.18 以降の dig と OpenSSL がある(dig の DoH 対応は BIND 9.18 からで、ビルドによっては無効の場合があります)
SMTP 向けの TLSA は「3 1 1」(DANE-EE、公開鍵、SHA-256)か、認証局の鍵を指す「2 1 1」がよく使われます。RFC 7672 では、SMTP で 0 と 1 の用途(PKIX-TA / PKIX-EE)を使わないよう求めています。この記事の監視スクリプトは「3 1 1」を前提にしています。
dig の版は次のように確認できます。
dig -v
# DoH が使えるかの簡単な確認
dig +https @1.1.1.1 example.com SOA +short
2行目で SOA が返らず「not supported」などと表示される場合は、その dig は DoH に対応していません。配布パッケージを新しくするか、Knot DNS の kdig(+https オプションがある版)を使います。
DoH で TLSA と検証結果を確かめる
まず手作業で確認します。ここでは Cloudflare(1.1.1.1)と Google(8.8.8.8)の公開リゾルバーに DoH で問い合わせています。リゾルバーの提供内容は変わることがあるため、利用条件は各事業者の案内で確認してください(2026年10月現在)。
dig +https @1.1.1.1 _25._tcp.mx.example.com TLSA
dig +https @8.8.8.8 _25._tcp.mx.example.com TLSA
見るべき点は三つです。
- ヘッダーの
status:がNOERRORになっているか。SERVFAILなら、DNSSEC の検証に失敗している可能性が高いです。 ;; flags:の行にadが含まれているか。含まれていなければ、そのリゾルバーは応答を「検証済み」として扱っていません。- ANSWER セクションの TLSA の値が、想定どおりか。
なお、dig は既定では DoH サーバーの証明書を検証しません。通信路の相手も確かめたい場合は、版によっては +tls-ca などの指定で検証できます。使える指定は手元の dig のマニュアル(man dig)で確認してください。
SERVFAIL が出たときは、+cd(検証を無効にする指定)を付けて再度問い合わせます。+cd を付けると値が返るなら、レコード自体は存在していて、署名の検証で落ちています。DS と DNSKEY の食い違いや、署名の期限切れを疑ってください。DNSViz のような可視化ツールで原因の層を絞ると早く進みます。
dig +https +cd @1.1.1.1 _25._tcp.mx.example.com TLSA
ブラウザやスクリプトから JSON で結果を見たい場合は、各社の JSON 形式の API も使えます。これは RFC 8484 の形式ではなく各社独自の仕様で、項目名や返し方が変わる可能性があります。
curl -s 'https://dns.google/resolve?name=_25._tcp.mx.example.com&type=TLSA'
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=_25._tcp.mx.example.com&type=TLSA'
応答の "Status": 0 が NOERROR、"AD": true が検証済みを表します。TLSA の値の書式は事業者によって異なることがあるため、値の照合には dig の出力を使うほうが扱いやすいです。
実際の鍵の指紋を求める
次に、MX が今提示している証明書の公開鍵から、「3 1 1」と照合する SHA-256 を計算します。
openssl s_client -starttls smtp -connect mx.example.com:25 </dev/null 2>/dev/null \
| openssl x509 -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -r
出力の先頭の 64 文字の16進数が、TLSA の4番目の値と一致していれば正常です。点検するマシンから 25 番の外向き通信が塞がれていることは珍しくありません(クラウドやプロバイダーの制限)。接続できない場合は、MX と同じネットワークにあるマシンか、25 番が開いている監視用サーバーで実行してください。
複数の MX がある場合は、それぞれのホスト名で TLSA と鍵を確認します。MX ごとに証明書が違う構成では、片方だけずれていても送信側によっては配送が保留されます。
監視スクリプトにまとめる
手作業の確認を cron で回せるようにまとめた例です。Cloudflare と Google の両方に DoH で問い合わせ、「NOERROR であること」「AD が付いていること」「実際の鍵の指紋が TLSA に含まれていること」を確認し、問題があれば終了コード 1 を返します。通知は cron のメールや、監視サービスへの通知に置き換えてください。
#!/usr/bin/env bash
# dane-doh-check.sh : DoH で TLSA を確認し、MX の実際の公開鍵と照合する(例)
set -u
MX="mx.example.com"
NAME="_25._tcp.${MX}"
RESOLVERS="1.1.1.1 8.8.8.8"
# MX が提示している公開鍵の SHA-256(TLSA 3 1 1 と照合する値)
actual=$(openssl s_client -starttls smtp -connect "${MX}:25" </dev/null 2>/dev/null \
| openssl x509 -noout -pubkey 2>/dev/null \
| openssl pkey -pubin -outform DER 2>/dev/null \
| openssl dgst -sha256 -r | cut -d' ' -f1)
if [ -z "$actual" ]; then
echo "NG: ${MX}:25 から証明書を取得できませんでした"
exit 2
fi
rc=0
for r in $RESOLVERS; do
out=$(dig +https @"$r" "$NAME" TLSA)
status=$(printf '%s\n' "$out" | grep -o 'status: [A-Z]*' | head -n1 | cut -d' ' -f2)
if [ "$status" != "NOERROR" ]; then
echo "NG: ${r} の応答が ${status:-不明} です"
rc=1
continue
fi
if ! printf '%s\n' "$out" | grep -Eq '^;; flags:[a-z ]* ad[ ;]'; then
echo "NG: ${r} の応答に AD ビットがありません(DNSSEC の検証に通っていない可能性)"
rc=1
fi
# 3 1 1 の値だけを取り出し、空白を詰めて小文字にそろえる
hashes=$(dig +https @"$r" "$NAME" TLSA +short \
| awk '$1==3 && $2==1 && $3==1 { h=""; for (i=4; i<=NF; i++) h = h $i; print tolower(h) }')
if printf '%s\n' "$hashes" | grep -qx "$actual"; then
echo "OK: ${r} の TLSA に現在の鍵が含まれています"
else
echo "NG: ${r} の TLSA に現在の鍵(${actual})がありません"
rc=1
fi
done
exit "$rc"
dig は長い16進数を途中で空白で区切って表示することがあるため、awk で4番目以降の欄をつなげています。DANE-TA(2 1 1)を使っている場合は、照合する対象が中間証明書や認証局の鍵になるので、このスクリプトはそのままでは使えません。
cron には、たとえば1時間おきに登録します。
# crontab -e(例)
17 * * * * /usr/local/bin/dane-doh-check.sh || echo "DANE の点検で問題を検出しました"
失敗したときに標準出力が cron のメールで届くよう、MAILTO を設定しておくと気づきやすくなります。
証明書の更新で TLSA を壊さない
DANE の障害で多いのは、証明書を更新したときに鍵が変わり、TLSA が古いまま残るケースです。監視で検知できても、配送が止まってからでは遅いので、更新手順のほうも見直しておきます。
- 「3 1 1」は公開鍵の指紋なので、鍵を使い回す更新なら TLSA は変わりません。certbot では
--reuse-keyを付けると更新時に同じ鍵を使います。 - 鍵を替える場合は、新しい鍵の TLSA を先に追加し、TTL の数倍の時間を置いてから証明書を差し替え、古い TLSA を最後に消します。
- 証明書の有効期間は短くなる方向で告知が出ています。更新の頻度が上がるほど手作業の入れ替えは事故のもとになるので、更新フックで TLSA の追加まで自動化するか、鍵の使い回しを選ぶかを先に決めておくと運用が安定します。
TLSA を書き換える前には、ゾーンファイルや DNS サービスの現在の設定を保存しておいてください。誤って消したときに、すぐ元に戻せます。
つまずきやすい点
- 公開リゾルバーの AD をそのまま信用する設計にしない:点検の目安としては有用ですが、送信側 MTA の判断を遠くのリゾルバーの AD に任せると、信頼の範囲が事業者まで広がります。MTA は手元の検証リゾルバーを使う構成が基本です。
- キャッシュで結果が遅れる:TLSA を更新した直後は、公開リゾルバーに古い値が残っています。TTL が切れるまでは「新旧どちらも入っている」状態を保つのが安全です。
- 問い合わせの頻度:公開リゾルバーには利用制限がある場合があります。点検は1時間に1回程度でも十分に役立ちます。
- DoH が使えない dig で動かしてしまう:古い dig は
+httpsを無視したり誤りとして扱ったりします。導入前に手動で一度実行し、期待どおりの出力になることを確かめてください。
まとめ
- DANE の安全性は、DNSSEC が正しく動くことと、TLSA が実際の鍵と一致することの二つで決まります。
- DoH は 443 番だけで公開リゾルバーに問い合わせられるので、TLSA と AD ビットを外から点検する道具として併用できます。
status、adフラグ、TLSA の値の三点を、複数の公開リゾルバーで確認します。SERVFAIL のときは+cdで署名の問題かどうかを切り分けます。- 監視スクリプトで実際の鍵の指紋と照合し、証明書の更新では「新しい TLSA を先に足し、古いものを後で消す」順序を守ります。