鍵の生成に一時ファイルは不要であり、それを省いても多くの解説が示唆するほどには直らない。省いて消えるのはファイルのライフサイクルと、その周りの後片付けの競合である。省いても手つかずのまま残るのは、後から秘密を漏らすあらゆる機会だ。これらのスクリプトの正当性の問題は通常もっと別の場所にあり、診断と鍵素材は別々のストリームを通る。

ファイルはあなたが思っていた露出ではなかった

一時ファイルは、誰でも読める場合、クラッシュを生き延びる場合、例外の後に後片付けの手順が飛ばされる場合に、具体的なリスクとなる。それらは現実であり、直す価値がある。それらはローカルで目に見え、テストも簡単だ。変更が単なる見た目だけだったと結論づける前に、自分のスクリプトでそれぞれを名指しせよ。

より静かなリスクは下流にあり、デバッグ中に出力される鍵、結果オブジェクト全体をシリアライズする例外、コアダンプを書くクラッシュハンドラ、ファイルのパスを受け取ってから中身を受け取るログ集約器がそれだ。一時ファイルを避けることはこれらのどれにも何もせず、そうでないと信じることが、善意の書き直しによって秘密が以前より露出したまま残る道である。変更は一連の改善の一つとして扱い、解決策とはみなさないこと。

ファイル名が与えられないときクライアントは何を書くか

クライアントの現行バージョンは、鍵コマンドが出力パスなしで与えられたとき、生成した鍵を標準出力へ送る。この文書化された挙動がメモリ内の手法を成立させ、ツールをデバイスのパスに向ける古い小技の必要を取り除く。

import subprocess
proc = subprocess.run(
    ["openvpn", "--genkey", "tls-crypt"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    check=True,
)
key_material = proc.stdout

コマンドはリストとして渡す。ここではシェルの展開は不要であり、シェルを有効にすると引数の境界が自分で制御できない引用符に依存してしまう。スクリプトが想定と異なる検索パスで動く可能性があるなら、意図した実行ファイルを絶対パスで使うこと。

戻り値の検査は、失敗した呼び出しの後に成功経路が続くのを防ぐ。それがなければ、空のバイト列がそのまま流れ、はるか後になって、鍵が使われる時点で失敗するが、そのときエラーメッセージは生成についてまったく触れていない。

二つのストリーム、二つの意味

標準出力は鍵を運ぶ。標準エラーはツールが伝えたいことすべてを運び、設定・バージョン・鍵の種類に関する警告も含む。両方を一つのバッファに流すのが、もっともらしく見える鍵の途中に文を縫い込んでしまう誤りである。

失敗の経路も分けておくこと。呼び出しが失敗したときは、失敗したコマンドと終了ステータスを報告し、そのうえで標準エラーのどの部分が表示しても安全かを意識して決める。結果オブジェクト全体をログに捨てるのはよくある近道であり、鍵を一緒に運びかねない。

後で素材をハッシュしたり比較したりするなら、バイナリ安全な読み取りが重要になる。想定外のエンコーディングのテキストモードは、テキストであるはずのないバイトを変えうるし、変わった鍵はハンドシェイクが失敗するまで間違った鍵と区別できない。

それでも保存するなら、意識して行うこと

ほとんどの配備は最終的にディスク上、ゲートウェイ上、あるいはシークレットストアに鍵を必要とする。作成時から制限的な権限で一度だけ書き、後からの chmod に頼らず、書き込みを原子的にして部分的なファイルが起動時にサービスに読まれるものにならないようにする。同じディレクトリ内のパスからの改名で通常は足りる。失敗側の分岐ではステージング用のパスを削除すること、失敗した書き込みこそ人々が忘れるコピーだからだ。

ファイルを作成するときにモードを設定し、ディレクトリのモードも制限的に保つ。バックアップ処理が何も考えずにさらっていく場所の下のパスは避けること、夜間バックアップの中の秘密はそのバックアップのあらゆるコピーの中の秘密だからだ。同じ理屈は他のサービスが読める共有ボリュームにも当てはまる。

共有鍵は身元ではない

この素材を生成しても証明書は発行されず、クライアントは登録されず、誰が接続しているかも確立されない。共有鍵はすでに互いを信頼している当事者間のチャネルを守り、身元は依然として証明書と、その手前に置かれた認証から来る。

鍵の種類も交換可能ではない。ある機能のための対称鍵は別の機能のためのそのままの代替ではないし、新しいリリースは両端で異なるファイルを持つ独自の変種を加える。設定が実際に名指しする種類を生成し、その種類が定める役割で使うこと。

結果はファイルサイズを見るのではなく、実際の接続で試すこと。ハンドシェイクが完了し、ログに鍵の警告が出なければ生成経路は正しく、鍵が統合されたストリームで切り詰められていたなら、接続はスクリプトではなく相手側を指す形で失敗したはずだ。

関連記事