OpenVPN 制御パケットを追う:TLS より前の tls-auth
tls-auth の HMAC は制御エンベロープ上にあり、TLS ハンドシェイクの内側ではない。鍵・方向・digest を揃え、保護を外さずに検証する。
tls-auth の失敗はふつうハンドシェイクの問題として報告されるが、実際には一度もそうだったことはない。HMAC は TLS の処理が始まる前に制御パケット上で検査されるため、鍵や方向の不一致は証明書の問題に見える失敗を生み、二層手前で判定される。エンベロープを独立した対象として扱えば診断は短くなる。フィールドを読み、検査が早い段階で走ることを確かめ、両端が一致させるべき三つを比較する。
エンベロープは組み立てられる順に読む
制御パケットはセッション識別子、任意の tls-auth HMAC、再送と確認の情報、信頼できるメッセージ識別子を運び、その後にメッセージ種別のペイロードが続く。メッセージがハンドシェイクのメッセージなら、ペイロードはたまたま TLS の暗号文である。関係はこれだけだ。TLS の素材は、エンベロープがすでに守っているメッセージの内側を通る。詳しくは OpenVPN のネットワークプロトコル解説 が制御チャネルのフィールドをその順に並べている。
信頼性の層を暗号と取り違えやすい。確認、再送、メッセージ識別子は、信頼できない転送の上で制御メッセージが一度だけ順序どおりに届くための仕組みである。認証ではなく、正しいメッセージ識別子はパケットを誰が送ったかを何も証明しない。
このまとまりは概念として捉えてほしい。バイト順の図ではなく、タグを計算するには足りない。HMAC に入るフィールドは実装・バージョン・モードに依存するからだ。
検査がハンドシェイクより前に走ることを示す
共有鍵を持つ受信側は、ハンドシェイクの状態を確保したり公開鍵の計算をしたりすることなく、HMAC を検証して偽造パケットを即座に捨てられる。保護は最初のハンドシェイクのパケットにも及ぶ。だからこそ、誤った鍵は遅れて現れる認証エラーではなく、接続の失敗として現れる。
デーモンの冗長度を上げ、tun デバイスではなく制御チャネルを観察する。
openvpn --config /etc/openvpn/server.conf --verb 6
探しているのは、TLS の警告より前に、パケットの段階で報告される制御チャネルの HMAC 検証失敗である。ログに TLS の警告が出ているなら、エンベロープは受理され、失敗は内側へ移っている。それは別の調査であり、別の対処になる。
方向の扱いもここにある。静的鍵ファイルを tls-auth とともに使う場合、両端は逆向きの鍵方向を取る。プロトコルはこの分割を使い、傍受したパケットを送信者へ反射できないようにする。方向を逆にすると鍵は同じままで、それでも全パケットが失敗する。片端ずつ変え、そのあと両方のデーモンを再起動して組を一緒に読み込ませる。
両端で鍵・方向・digest を揃える
三つの値が一致する必要があり、ファイルなのはそのうち一つだけである。静的鍵の中身は両端で同一でなければならず、方向は逆向きでなければならず、HMAC に使う digest も一致しなければならない。鍵の素材に触れる前に設定を確認する。逆にした方向が一つあるだけで、壊れたファイルより多くの失敗を説明できる。
grep -E 'tls-auth|tls-crypt|key-direction|auth ' /etc/openvpn/server.conf
共有の秘密はエディタではなくツールで生成し、権限は厳しく保つ。
openvpn --genkey secret /etc/openvpn/ta.key
chmod 600 /etc/openvpn/ta.key
古い版は鍵生成のオプション順が少し違っており、その版向けに書かれたプロファイルが今も出回っている。生成されるファイルはどちらでも同じ種類の対象なので、確認すべきは両端が同一のバイトを持つかどうかであり、どの綴りで作ったかではない。
tls-auth と tls-crypt を混同しないこと。どちらも制御チャネルを守るが、tls-crypt はさらに暗号化し、ハンドシェイクを観測者から隠す。そして方向の分割を独自の鍵の扱いに置き換える。一方を使うプロファイルは、他方向けに設定された相手とは相互運用できず、digest をどう調整してもその隔たりは埋まらない。
タグは本物の形式からだけ再現する
バイト単位の再現は VPN の運用とは別の作業であり、本物の入力があって初めて成り立つ。パケットを組み立てた実装とバージョン、選んだ digest、方向、そしてセッション識別子と確認の配列を含む実際のバイトである。フィールド一覧を文章にしたものは、そのバイトではない。
タグの長さは digest から決まるので、固定長という前提は誰かが変えた瞬間に崩れる。ブログ記事の表が示すとおりではなく、実装がするようにパケットをハッシュし、手で導いた期待値ではなく同じ相手から取ったキャプチャと比べる。
この演習は本番のプロファイルから遠ざけておく。タグを再現しても、配備で保護を有効にすべきかどうかは分からない。
保護を外さずに不一致を巻き戻す
誘惑される試験は、tls-auth を消して接続が通るのを見て、鍵が間違っていたと結論することだ。その答えは保護を失うだけで、三つの値のどれが食い違っていたかを何も教えない。代わりにサーバ側から承認済みの一致する設定を取り寄せ、それと比較する。
変更は元に戻せる形で用意する。以前のサーバ設定と鍵ファイルを残し、修正した組を適用し、クライアントがハンドシェイクを完了することを確かめ、それから初めてバックアップを消す。変更後にクライアントが失敗するなら、障害の最中に第三の案を即興で作るのではなく、以前のファイルを戻す。復旧は、始める前にすでに知っている一つのコマンドであるべきだ。
最後に、通った検査が何を意味するかを忘れないこと。相手が共有鍵を持っていることを示すのであって、その証明書やアカウントが許可されていることを示すのではない。そして交渉されたデータチャネルの暗号については何も語らない。許可とデータの保護はもっと後に確立される。だから清潔なエンベロープ検査は出発点であって、判決ではない。