ハッシュは変化を、署名は誰かを保証する
一言でいうと
持ち込みは、「ファイルを移す作業」ではなく、申請書 → 取得 → 一覧 → 署名 → 媒体 → 検証 → インストール → 記録という手続きです。ハッシュは、移す間に変わっていないかを、署名は、その一覧を誰が作ったかを、保証します。どちらか片方だけでは、不十分です。
なぜ必要なのか
エアギャップ環境への持ち込み審査で、よくやり取りされるのは、USB 1本とメール1通です。USBの中に、ハッシュの一覧ファイルが一緒に入っていると安心しますが、その一覧は、ファイルを書き換えた人が、一緒に直せます。転送中の破損は検出できますが、すり替えは検出できません。逆に、署名だけしてハッシュの一覧がないと、何に署名したのかがあいまいです。そして、審査する側が「バージョンは最新で」という申請書を受け取ると、外部で取得した日と審査の日の間に、新しいバージョンが出て、一覧と実物がずれます。
どう動くのか
申請書は正確なバージョンで: requestsではなくrequests==2.32.3、rsc.io/quoteではなくv1.5.2です。範囲や「最新」を書くと、取得する日ごとに結果が変わり、審査の記録と持ち込み物が一致していることを、誰も証明できません。依存関係は、申請書に人が書くのではなく、取得するツールが解決した結果(pipのwheelの一覧、Goのgo.sum)を、一覧に付けます。
一覧は相対パスで: sha256sumは、一覧に書かれたパスを、現在のディレクトリを基準に開きます。バンドルのディレクトリの中で、find . -type fで作成すれば、バンドルをどこに展開しても、その中でsha256sum -cが成立します。一覧ファイル自身と署名ファイルは、一覧から外します。
署名は一覧に行います。ファイル数百個に、1つずつ署名する必要はなく、ハッシュの一覧1枚に署名すれば、その一覧が、すべてのファイルを束ねます。OpenSSL 3のgenpkey -algorithm ed25519で鍵を作成し、pkeyutl -sign -rawinで署名し、pkeyutl -verify -pubin -rawin -sigfileで検証します(Ed25519は、ハッシュを別に選ばず、原文をそのまま受け取ります)。GPGやminisignを使う組織も多いですが、原理は同じです。
秘密鍵と公開鍵は別の経路で: 秘密鍵は、ダウンロード側の担当者のPCから出ません。公開鍵は、媒体ではない経路(事前登録、別の文書)で、エアギャップ環境側にあらかじめ届いている必要があります。媒体に公開鍵を一緒に入れると、すり替えた人が、自分の公開鍵を入れれば済んでしまいます。
検証の順序は署名が先です。エアギャップ環境側は、(1)事前に登録された公開鍵で、一覧の署名を確認し、(2)その一覧で、ファイルのハッシュを確認します。順序を逆にして、ハッシュから見ると、すり替えられた一覧が、自分のファイルと問題なく合うことを、「合格」と読んでしまいます。そして、検証スクリプトは、失敗したとき、0以外の値で終了する必要があります。そうしないと、次の段階(インストール)が止まりません。メッセージだけを出力して0で終わる検証は、自動化では検証ではありません。
現場での姿
このラボのイメージで、requests 2.32.3(wheel 5つ)とrsc.io/quote v1.5.2(モジュールzip 3つ、ほとんどがx/textの4.8MB)を、1つのバンドルにまとめると、ファイルが48個、5MBあまりでした。wheel 5つとモジュールzip 3つのほかに、Goのcache/downloadに積まれる補助ファイル(.mod・.info・.ziphashと、チェックサムデータベースの照会記録)も、一緒に入るからです。一覧は、人ではなく、findが作る必要がある理由です。ここに、SHA256SUMS 1枚と、64バイトのEd25519署名1枚が付きます。採点は、皆さんの検証スクリプトを、バンドルのコピー2つに対して実行します。1つは、wheel 1つの1バイトを変えたもの(ハッシュで引っかかる必要があります)、もう1つは、変えたファイルに合わせて、SHA256SUMSまで直したもの(署名で引っかかる必要があります)です。2つ目を通してしまうスクリプトは、すり替えを防げません。
記録も、手続きの一部です。何を申請して、バンドルにファイルがいくつあって、一覧自体のハッシュは何で、どの鍵で署名を確認して、何をインストールしたかを残して初めて、あとで「あのとき入ってきたものは何だったか」に答えられます。
次のラボですること
申請書を書いて、Pythonのwheelと、Goモジュールを、1つのバンドルに取得して、SHA256SUMSと、Ed25519署名を付けます。秘密鍵を除いた媒体(tar)を作成して、エアギャップ環境側に展開し、署名が先でハッシュが次の順序の検証スクリプトを書きます。採点が、そのスクリプトを、すり替えたコピーに実行してみます。最後に、外部をふさいだまま、バンドルだけでインストールして、持ち込み記録をJSONで残します。
参考ドキュメント: OpenSSL genpkey・OpenSSL pkeyutl・GNU coreutils sha256sum・pip download・Go Modules Reference