DNSSEC と DNS のセキュリティ

21 分で読了
このページの目次

リゾルバが A レコード、公開鍵、および署名を同時に取得し、署名検証が成功したとしても、結果を直ちに信頼することはできません。攻撃者も独自の鍵を生成し、偽のアドレスに署名することは可能だからです。DNSSEC の鍵は、対象 RRset の署名鍵をローカルで信頼されたアンカーに接続し、署名、否定証明、有効期間のすべてが成立しているかどうかを判断することにあります。

問題核心的な判断基準
データは誰が認可したか信頼アンカーから対象 RRset までの認証チェーン
RRSIG があるからといって安全か署名の数学的正当性と鍵の信頼性は別々に検証する
存在しない名前はどのように署名するかNSEC/NSEC3 により認証された否定証明を提供する
署名があるのに SERVFAIL になるのはなぜかDS、DNSKEY、署名時刻、アルゴリズム、ネットワークのすべてを確認する必要がある
DNSSEC はクエリを隠すのか機密性は提供しない。暗号化された転送が別の役割を担う
TLSA がすべての CA を直接置き換えるのかusage、DNSSEC の状態、およびアプリケーションの検証仕様による

セキュリティ目標:RRset の認証であり、ビジネスの善意を保証するものではない

DNSSEC は、DNS データの送信元認証、完全性、および認証された不存在証明を提供します。署名の基本単位は RRset であり、各 RR が個別に署名されるわけでも、ID、Flags、任意の追加データを含む応答パケット全体が統一して署名されるわけでもありません。キャッシュや別の権威サーバーが同じ署名済みデータを配信しても、検証は特定の物理サーバーへの接続を要求しません。RFC 4033:能力と境界

DNSSEC は、名前、アドレス、またはクエリ時刻を暗号化するものではなく、サービスの可用性を保証するものでもなく、ドメイン運営者が悪意を持っていないことを証明するものでもありません。ドメイン所有者が正しく署名した誤った A レコードであっても、それは認証された誤った設定です。秘密鍵やレジストラのアカウントが侵害された場合もまた、「途中改ざんの防止」という能力の範囲を超えています。

従来の DNS におけるランダムなトランザクション ID、ソースポート、応答関連性、およびキャッシュ適格性チェックは、なりすましの難易度を高めるものですが、暗号学的な送信元認証ではありません。DNSSEC はこれらのチェックと補完関係にあり、有効化してもすべてのリゾルバ保護を解除できるものではありません。

4種類のレコードの役割

種類保存内容検証時の役割
DNSKEY, 48flags, protocol, algorithm, 公開鍵候補検証鍵を提供する
RRSIG, 46署名対象タイプ, アルゴリズム, 時刻, key tag, 署名など正規化された RRset の検証を行う
DS, 43key tag, 鍵アルゴリズム, ダイジェストタイプ, ダイジェスト親ゾーンからの認可を子ゾーンの DNSKEY に接続する
NSEC / NSEC3, 47 / 50名前またはハッシュ範囲, タイプビットマップなど否定応答と委譲状態の証明をサポートする

RRSIG には Original TTL が含まれています。これはキャッシュ転送において TTL が減算されるためです。正規化された署名検証には、規定された元の値、名前形式、およびソート順序を使用する必要があります。Key Tag は候補鍵をフィルタリングするための短い識別子であり、衝突の可能性があり、暗号学的な指紋として扱うことはできません。RFC 4034:レコード形式と正規化

DS ダイジェストの入力は、正規化された owner name の wire encoding と DNSKEY RDATA の連結です。DNSKEY RDATA には Flags、Protocol、Algorithm、および公開鍵のバイト列が含まれます。公開鍵だけをハッシュすることはできず、Base64 形式のテキストをハッシュすることはさらにできません。ダイジェストアルゴリズムは Digest Type によって決定され、すべての DS が固定で SHA-256 であるわけではありません。

信頼チェーン:子ゾーンの鍵セット検証を省略しない

ゾーン跨ぎごと:信頼できる親ゾーンの署名 → DS ダイジェストの一致 → 子ゾーンの鍵セット 信頼済み親ゾーン DNSKEY 信頼アンカーまたは上位チェーンから取得 親ゾーン内の子ゾーン DS DS をカバーする RRSIG を最初に検証 一致する子ゾーン DNSKEY 正規化された owner + DNSKEY RDATA 子ゾーン DNSKEY RRset の検証 認証済みの鍵によって署名検証 対象 A / AAAA などの RRset の検証 子ゾーンの信頼された鍵セットを使用 公開鍵をダウンロードして自己署名の検証に成功したからといって、信頼アンカーに接続されたわけではありません。DS ダイジェストの一致だけで鍵セットの検証を省略することはできません。

信頼アンカーは、事前に信頼された DNSKEY または DS 情報として表現できます。デプロイメントはルートからグローバルチェーンを構築することもできますし、他のゾーンのアンカーを信頼として設定することもできます。固定された「ルート DNSKEY のハッシュ 1 つ」というわけではありません。ルートから対象ゾーンまでの例では、親ゾーンの DS 署名検証、子ゾーンの DNSKEY ダイジェスト一致、子ゾーンの DNSKEY RRset 署名検証を繰り返し実行し、最終的に対象レコードを検証します。

子ゾーンの DNSKEY をダウンロードして直接 A レコードの署名を検証しても、「この公開鍵の秘密鍵が署名した」ということしか示せません。信頼された認可がある公開鍵であることを最初に証明して初めて、安全な結果が導き出せます。親ゾーンに保存される DS は親ゾーンによって署名され、子ゾーンの DNSKEY RRset は子ゾーンの対応する鍵によって署名されます。これら 2 つの役割を逆にすることはできません。RFC 4035:検証プロセス

検証状態説明アプリケーションへの意味
Secure必要な認証チェーンと関連検証が成立しているDNSSEC の信頼モデル下でデータを認証する
Insecure署名チェーンが構築されていないことが確実である(例:認証された DS なし委譲)署名が破損したわけでも、認証が成功したわけでもない
Bogus信頼関係に基づけば検証可能であるはずが失敗した検証リゾルバは通常、正常な結果として配信しない
Indeterminate信頼チェーンがあるかどうかを決定できない強制的に Secure として扱うことはできない

「DS が受信できない」ことは、「DS が存在しない」という確実な証明を得たことにはなりません。前者はタイムアウト、切り捨て、またはフィルタリングの可能性もあります。同様に、既存の信頼アンカーに接続されていない孤立した署名済みゾーンは、RRSIG があるからといってインターネット全体がそれを検証できると主張することはできません。

TTL、署名時刻、および時計

キャッシュの TTL と署名の有効期間は異なる制約である レコード TTL: 3600 秒 対応するキャッシュは最大 1 時間 署名の残り時間: 900 秒 絶対的な有効期限は現在から 15 分後 有効なキャッシュ上限 ≤900 秒 他の TTL 制約も考慮する必要がある 期限切れ間近の同じ署名を再取得しても、絶対的な有効期限は先には延ばされない 署名がまだ有効になっていない、期限切れ、またはローカル時計のエラーは、本来解析可能なゾーンでも検証に通らなくする可能性があります。

RRset の TTL=3600 秒を受け取り、署名の残り時間が 900 秒しかない場合、署名が現在有効であっても、検証キャッシュは 3600 秒を無条件で再利用することはできません。有効なキャッシュ寿命は、RRSIG TTL、Original TTL、および署名の残り時間などの制約によっても影響を受けます。RFC 4035:認証後のキャッシュ

署名の Inception/Expiration は絶対的な有効期間であり、TTL はキャッシュの相対的な寿命です。これらは、再取得を繰り返すことで互いにリセットされるものではありません。事前の再署名と適切な時刻同期は可用性に影響します。ローカル時計の大幅なずれは、本来正常な複数のゾーンが、同時に「署名がまだ有効になっていない」または「期限切れ」として表示される原因となります。

署名は検証できたのに、なぜ Secure にならないのか?

対象の署名が正しいと仮定し、DS の一致または鍵セットの検証を個別に無効化します。最後のブロックで信頼できるパスが構築されていないため、Secure にはなりません。さらに時刻を有効期限前に移動すると、寿命は署名の残り時間によって圧縮されます。t=2900 の時点では、時間条件は通過できますが、キャッシュ寿命の上限はすでにゼロです。その後、署名の有効期間を超えます。

図を準備しています
署名は検証できたのに、なぜ Secure にならないのか?

対象の署名が正しいことは、最後の条件に過ぎません。認可チェーンの欠落、鍵セット検証の失敗、または時間の無効化は、対象自身の署名では補えません。

KSK、ZSK およびローテーション:役割は固定されたアルゴリズムの等級ではない

KSK は通常、DNSKEY RRset の署名を担当し、ZSK は通常、ゾーン内の他の権威 RRset の署名を担当します。combined signing key を 1 つ使用する也可以选择です。DNSKEY の Zone Key マスクは 0x0100、SEP は 0x0001 です。一般的な flags=256/257 はこれに由来します。SEP は役割のヒントであり、「この鍵は DNSKEY しか署名できない」という暗号学的な権限制限として扱うことはできません。

プロトコルは、KSK が常に 2048 ビット RSA で、ZSK が常に 1024 ビットであること、または年/月単位で交換する必要があることを規定していません。鍵アルゴリズム、署名の更新、および鍵の交換は異なる選択です。同じ鍵を複数回再署名することは可能であり、署名が期限切れだからといって、同時に鍵を交換しなければならないわけではありません。RFC 6781:DNSSEC 運用

ローテーションでは、古い DNSKEY、古い DS、古い署名がそれぞれ異なるキャッシュ内にまだ存在する可能性があることを考慮する必要があります。一般的なアプローチは、新しい鍵の公開とキャッシュ伝播の時間を確保し、重複期間中に必要な組み合わせがすべて検証できるようにし、その後、対応するローテーション計画に従って古い材料を退役させることです。「まず古い key を削除し、次に新しい DS をアップロードする」と一般化することはできません。これにより、古い DS をまだ信頼している検証ノードは子ゾーンの鍵と一致しなくなります。

管理画面での成功表示だけでなく、親ゾーンの DS 変更と各権威ノードの実際の応答を記録すべきです。退役の待機時間は、関連する TTL、署名の有効期間、および計画によって決定され、すべてのプロバイダーに適用できる固定の時間表を本稿で作成するものではありません。

NSEC/NSEC3:否定応答に対して検証可能な根拠を提供する

ゾーン内で DNS 仕様に従って名前の順序が隣接している存在する名前が a.example. と c.example. であると仮定します。署名済み NSEC はこれらの 2 つの名前を関連付けることで、その間に b.example. がないことを証明するのに役立ちます。名前は存在するが特定のタイプがない NODATA については、タイプビットマップが異なる証明情報を提供します。

これは直感的な範囲理解に過ぎず、完全な NXDOMAIN 証明には、closest encloser、ワイルドカードなどの条件を適切に処理する必要があります。単に文字列の辞書順範囲を 1 つ見つけただけで、証明が完了したと宣言することはできません。NSEC は DNS 名の規範的な順序を使用しており、任意の言語のデフォルトの文字列ソートとは異なります。

NSEC3 は規範的な名前にハッシュを適用し、対応する範囲を形成することで、チェーンから名前を直接読み取るコストを増加させますが、短く一般的な名前は辞書攻撃によって推測される可能性があります。Opt-Out を使用すると、対応する未署名委譲をハッシュチェーンに個別に含める必要がなくなり、特定の証明境界を持ちます。これは任意のレコードの署名免除スイッチではありません。RFC 5155:NSEC3

反復回数を増やすと検証作業量が増加しますが、大きいほど安全であるという一般的なチューニング項目として扱うことはできません。RFC 9276 は、NSEC3 で追加反復ゼロと空のソルトなどの設定の使用を推奨しています。パラメータは、使用されている署名/検証ソフトウェアのサポートと検証コストを考慮して選択する必要があります。RFC 9276:NSEC3 パラメータ

DO、AD、CD:フラグをキャッチしたからといって自己検証するわけではない

DO フラグは、DNSSEC データの能力/ニーズをクエリに関連して表現します。dig +dnssec は RRSIG を表示できますが、それだけで自動的に完全なローカル検証ノードになるわけではありません。AD は、検証ノードによる認証状態の宣言です。クライアントがポリシーに従ってこれを受信するには、その検証ノードを信頼し、そのノードへのパスに対応する保護がある必要があります。CD リクエストは対応するチェックを無効にし、問題の対照用であり、データを信頼できるものにするものではありません。

権威元の生レコード、再帰器の結果、およびローカル検証ノードの詳細な診断を個別に観察できます。SERVFAIL が発生した場合、ネットワーク障害、切り捨て処理、またはリソース制限が原因である可能性もあり、必ず DNSSEC の失敗であると断定することはできません。Extended DNS Errors は理由を補足できますが、すべての応答に含まれるわけではなく、自身の信頼判断を置き換えるものではありません。RFC 8914:EDE

現象最初に確認すべき項目
DS は存在するが、一致する DNSKEY が見つからない親/子ゾーンの公開状態が一致しているか、キャッシュが古い値を保持していないか
鍵は一致するが、対象の署名検証に失敗したRRset の正規化、署名、アルゴリズム、および権威ノードの一貫性
複数の正常なゾーンで突然時刻エラーが発生した検証側の時計と時刻同期
小さな応答は正常だが、大きな DNSSEC 応答が失敗したEDNS、UDP 断片化、および TCP フォールバックパス
特定の再帰器のみが失敗したアンカー、キャッシュ、サポートされているアルゴリズム、および具体的なエラー情報

DANE:usage に応じて TLS の信頼ルールを決定する

TLSA の owner には通常、ポートと転送プロトコルが含まれます(例: _443._tcp.example.com.)。RDATA には certificate usage、selector、matching type、および関連データが含まれます。selector は完全な証明書と SubjectPublicKeyInfo を区別し、matching type は原文一致とダイジェスト一致を区別します。RFC 6698:TLSA

usage名称概要
0PKIX-TAPKIX チェーン検証の基礎の上に CA を制約する
1PKIX-EEPKIX 検証の基礎の上に終端証明書(エンドエンティティ)を制約する
2DANE-TADNSSEC 認証された関連情報を使用して対応する信頼アンカーを構築する
3DANE-EEDNSSEC 認証された関連情報を使用して終端証明書/公開鍵を制約する

したがって、DANE は一律に「PKIX チェックが不要になる」と等価ではありません。アプリケーションは、サービス名を正しくバインドし、Secure な TLSA を取得し、TLS による対応する秘密鍵の所持証明およびこの usage のルールを完了させる必要があります。単なるハッシュ一致では不十分であり、usage=3 でも要件の優先順位に基づいて「最大限のセキュリティ」として離れて扱うことはできません。

DANE には SMTP 用のアプリケーション仕様があり、一般的なブラウザのサポートが限られているからといって、採用率がゼロであると主張することはできません。SMTP において MX の発見方法、TLSA の可用性の判断、および失敗時の処理は、メールシーケンスの仕様によって補完されます。RFC 7672:SMTP DANE

ローカル再計算:DS ダイジェストの入力と時間の制約

import hashlib
import struct

def owner_wire(name):
    return b''.join(bytes([len(x)])+x.encode('ascii')
                    for x in name.rstrip('.').lower().split('.'))+bytes(1)

# 教育的なバイト列のシミュレーション 32 B 公開鍵フィールド;秘密鍵の生成や実際の署名は行わない。
public_bytes = bytes(range(32))
rdata = struct.pack('!HBB',257,3,15)+public_bytes
owner = owner_wire('Example.COM.')
digest = hashlib.sha256(owner+rdata).digest()
assert len(digest)==32
assert digest == hashlib.sha256(owner_wire('example.com.')+rdata).digest()
assert digest != hashlib.sha256(public_bytes).digest()
assert digest != hashlib.sha256(owner_wire('other.example.')+rdata).digest()
changed_flags = struct.pack('!HBB',256,3,15)+public_bytes
assert digest != hashlib.sha256(owner+changed_flags).digest()
assert 257&0x100 and 257&1
assert 256&0x100 and not 256&1
# 時間のシーケンス番号ラップオーバーを跨がない教育的な値;完全な検証には他のチェックもある。
now,inception,expiration = 2000,1000,2900
def signature_time_valid(at):
    return inception <= at <= expiration
assert signature_time_valid(now)
assert signature_time_valid(expiration)
assert not signature_time_valid(expiration + 1)
assert min(3600,3600,3600,expiration-now)==900
assert min(3600, 3600, 3600, expiration - expiration) == 0
assert not signature_time_valid(3000)
print('DS 入力には owner/flags、公開鍵のエンコーディングの違い、フラグ、および有効期間の制約チェックが含まれている')

この例は、エンコーディングの依存関係と時間の予算のみを検証し、RRSIG の暗号学的な署名検証や実際の信頼チェーンの構築は行いません。DNSKEY/DS の公開、本番鍵のローテーション、またはリゾルバポリシーの変更は行っていません。

継続的な参照