信用とは何か ― ログイン画面から始まった問い

ログイン画面を書いていて、ふと思った。

これもプログラムだ。IP制限をかけて一人しかアクセスできないようにしたとする。それでも「脆弱じゃないものなんてどこにもない」と言われる。なぜだろう。

そして、その先にある問い。信用とは、結局何なのか。

これは技術記事ではない。実装の話は別に書いた。こちらは、手を動かしているあいだずっと引っかかっていたことについての記録だ。


一枚のドアを完璧にしても

ログイン画面を完璧に書けたとしても、それはドアの一枚でしかない。

IP制限をかけたとき、実際に守っているものと、暗黙に信じているものを並べると差がはっきりする。

IP制限を実行しているのはファイアウォールかリバースプロキシかアプリのコードで、それ自体がまた別のプログラムだ。設定ミスもバグもある。IPアドレスは身元ではなく経路情報で、NAT配下なら同じ会社の別の人も同じIPに見える。その人のPCが感染していれば「正しいIPからの正しいログイン」になる。

その下にはOS、TLSライブラリ、カーネル、仮想化基盤、クラウドの管理コンソール、DNS、ドメインレジストラがある。横にはSSH、バックアップ、CI/CDのデプロイ経路、同じデータを見ているステージング環境、ログファイルがある。さらに横には、パスワード再発行を受け付けるサポート窓口という「人間のAPI」がある。

ログイン画面を突破する必要はどこにもない。 攻撃側は一箇所見つければよく、防御側は全部塞ぐ必要がある。この非対称性が「絶対安全」を言えなくしている。

加えて時間の問題もある。今日の依存ライブラリに明日CVEが出れば、コードを一行も変えていないのに脆弱になる。安全性はコードの静的な性質ではなく、環境と時間に依存する状態だ。

形式検証をしても消えない。seL4 のように数学的に正しさを証明したカーネルもあるが、あれは「仕様に対して、ハードウェアがモデル通りに振る舞うという仮定のもとで」正しい。Spectre や rowhammer は、まさにその仮定のほうを壊した。証明は不確実性を消すのではなく、不確実性を仮定の側に押し込んで見えるようにする作業だ。


信用の実務的な定義

セキュリティ用語には面白いねじれがある。TCB (Trusted Computing Base) の "trusted" は「信頼できる」ではなく「裏切られたらこちらが終わる部分」という意味だ。褒め言葉ではなく、危険の指定。

そこから逆算すると、定義はこうなる。

信用とは、検証をやめた場所のこと。

コンパイラの出力を毎回逆アセンブルして確認していないし、CPUが仕様通りに動くか検証していないし、クラウド事業者の中の人が覗いていないことを確かめていない。確かめていないのに、壊れたら困る。それが信用だ。

Ken Thompson の "Reflections on Trusting Trust" (1984) がこれを突きつけた。バックドアを仕込んだコンパイラが、自分自身をコンパイルするときにそのバックドアを再生産するように作れる。ソースコードを全部読んでも見つからない。結論は「自分で完全に書いていないコードは信用できない」で、しかも全部自分で書くことは不可能なので、事実上「どこかで信じるしかない」という話になる。

信用は対象の性質ではなく、こちら側の判断だ。 検証コストと、失敗したときに失うものを天秤にかけて、「ここから先は確かめない」と線を引く行為。


もどかしさの正体

設計を詰めていく途中で、強いもどかしさに当たった。

何かに依存していて、それが壊れたとき、何もできず眺めるしかない。いや、壊れたことを知る由もない。 そこが一番きつい。

これは実装の不足ではなく、定理だった。

「何も起きていない」と「通知が届かなかった」は、受信側から区別できない。 信頼できない通信路の上では、双方が同じ事実を確実に共有することは有限回のやりとりでは達成できない。二人の将軍問題だ。

だから「沈黙 = 平常」という設計は、定義上必ず壊れる。そして攻撃者が最初にやることは通知を止めることなので、一番危険な瞬間に一番静かになる。

もどかしさは、正しく機能していた。仕組みの限界を、感覚が先に検知していた。


反転させる、そして限界

解は極性の反転だった。沈黙を安全側に置くから詰むので、沈黙を危険側に置く。

サーバーが定期的に署名付きの「正常です」を出し続け、それが止まったら警報とする。デッドマンスイッチ、ワラントカナリアと呼ばれる構造だ。攻撃者は「余計なものを送らせない」ことはできても、「出続けるべきものを出させ続ける」ことはできない。

イベントをハッシュチェーンで連結すれば、サーバーが完全に乗っ取られていても過去のイベントを消せない。消せば連鎖が切れて検証が失敗する。Certificate Transparency がやっていることと同じで、CAを信用させるのをやめて、CAが嘘をついたら必ずバレる構造にした。

構造としては正しい。しかしここで壁に当たる。

これを実行できるユーザーは、ほぼいない。

cron を書ける人が何パーセントいるか。ハッシュチェーンの検証結果を見せても、一般ユーザーは意味が分からず「壊れた」と判断してブラウザを閉じる。エンジニア向けの解を、大衆向けの問題に貼っていた。


土俵の話

そして、もっと大きな問題がある。

Apple のサイドローディング制限、Play Integrity API、通知権限の締め付け。これらは全部「安全」の名目で導入され、実際に安全性を上げている。同時に、プラットフォームを経由しない配信経路を消している。 この二つは矛盾しない。攻撃面を減らす最も確実な方法は、通り道を減らすことだからだ。

信頼できないものを排除していくと、Apple と Google の許可した経路しか残らない。「大企業を信用しない設計」を作ろうとしても、それを動かす場所が大企業の土俵の上にしかない。

これは技術で解けない。 ここに来た時点で、扱っているのは統治構造の問題だ。

実際に何かが動いた事例は、全部法制度側から来ている。EU の DMA によって Apple は代替アプリストアとサイドローディングを開放した。Apple は2024年に EU での PWA サポートを削除しようとして、規制圧力で撤回した。この撤回がなければ、Web で構築しようとしているものは EU で成立しなくなっていた。

Web が生き残っているのは技術的必然ではなく、政治的な綱引きの現在の位置でしかない。

そして、これを肌で理解して防御に組み込めるのは、実質エンジニアだけだ。どのサードパーティが危険か、企業がどういう戦略を取っているか、それを日常的に追っている人間しか判断できない。大衆向けサービスにおいて、ユーザー自身に検知を期待するのは構造的に無理がある。


大衆と、構造を見る視点

ここで、視点の差についても書いておきたい。

自分が見ているのは表面ではなく構造だ。「これは安全です」と言われても意味がなくて、どうして安全なのか、なぜそうしたのかを見る。 AI が出した設計であっても、出してきたから安全だとは一つも思っていない。根拠の形を見て、確かめられる形になっているかを見る。

この見方は、大衆から見れば偏っている。fsync を挟む理由や、通知が沈黙する構造の話は、大抵「そこまで要る?」で終わる。締切と要件がある以上、それは正しい判断でもある。

ただ、偏りの中身は「間違っている」ではなく「解像度が合っていない」だと思う。大衆向けサービスの標準的な粒度から見れば深すぎるが、認証プロトコルを設計している人たちの間では普通の会話だ。偏っているのではなく、その解像度を要求する場所が限られている。

個人で開発する理由の一つがそこにある。この深さで考えることが目的なら、それを許す形態は個人しかない。逃避ではなく、条件の合う環境を選んでいるだけだ。

そしてこの分野に限れば、疑い続ける人がいないと壊れる。全員が前提を受け入れる集団は、前提が間違ったときに誰も気づかない。持て余している性質は、この領域では機能の側にある。


根源にあったもの

設計の判断を全部並べてみて、共通点に気づいた。

メール認証を外したのは、送信経路とプロバイダという他人を外すため。DBを使わずファイルにしたのは、他人が書いたレイヤーを一つ減らすため。フレームワークを使わないのは、読み切れないコードを持たないため。運営が責任を引き受けると決めたのは、ユーザーに検知を委ねないため。

全部、判断の主体を自分に寄せる操作だった。

根源はこれだ。

自分に振り回されるのは許せるが、他人が原因のミスで振り回されるのが一番許せない。

自分のミスは、原因を知っていて、直せて、二度目を防げる。他人のミスは、いつ来るか分からず、来たとき何が起きたか分からず、直せず、待つしかない。同じ「壊れた」でも、片方は作業で、片方は無力だ。質が違うものを同じ「リスク」という言葉で扱っているから、言語化しにくい。

最初の「なぜ脆弱じゃないものはないと言われるのか」という問いも、技術的な疑問というより、この不快感の入口だった。「絶対安全はない」が諦めろという意味なら受け入れられない。でも自分で手を動かせる範囲がまだ残っているなら、それはただ範囲の話でしかない。


依存の種類は選べる

依存はゼロにできない。でも依存の種類は選べる。

「壊れたら止まる依存」と「壊れても気づかない依存」は、まったく別物だ。

ライブラリの CVE は公開されるし、TLS の障害は接続失敗として見える。一方、メール配送の失敗は沈黙するし、通知基盤の停止も沈黙する。設計から削っていったのは、後者ばかりだった。

つまり「他人に振り回されたくない」の実装形は、依存を消すことではなく、沈黙する依存を消すことだった。前者は不可能だが、後者はかなりのところまで到達できる。

もう一つの軸が、相関だ。

ライブラリの共通脆弱性は、報告があっても防御にならない。Log4Shell では報告は正しく出たが、同時に世界中の全 Java サーバーが対象になり、公開の瞬間から攻撃が始まった。報告が公開された瞬間が、攻撃開始の合図でもある。 防御側は自分のスケジュールで動けず、攻撃側の時計に合わせて叩き起こされる。Heartbleed も同じだった。

これは「他人のミスで振り回される」の最も純粋な形だと思う。

自作コードにバグがあれば、それは自分一人の問題で、狙う価値もなく、公開スケジュールもない。量は増えるが、相関がない。


委譲のチェーン

大企業のアカウント復旧を見ていると、結局「別のアカウントサービスで鍵を受け取る」構図に見える。

パスワードを忘れたらメールに送る。メールを忘れたら SMS を送る。番号は SIM に紐づいていて、SIM スワップで奪われる。パスワードマネージャに預ければ、マスターパスワードの復旧が要る。

チェーンは必ずどこかで終わる。 終端はたいてい、大企業のサポート窓口にいる人間だ。暗号でも数学でもない。

だから「大企業だから安全」ではなく、大企業は終端に人間とプロセスを置ける規模がある、というだけの話だ。個人開発者には置けない。土俵の問題と同じ構造がここにもある。

メールプロトコル自体も同じだ。SMTP には認証も暗号化も元々なく、後から SPF/DKIM/DMARC を貼って回っている。「メール認証」は、認証されていない仕組みの上に載った認証だ。 それが業界標準なのは、安全だからではなく、全員が持っているからだ。

SMS が許せなかった理由

2017年ごろ、中学生の自分は SMS 認証を「絶対に問題がある」と確信していた。言葉にはできなかったし、始まりは「不便だから嫌だ」だった。

後から知ったが、NIST が SP 800-63B で SMS を非推奨方向に位置づけたのが 2016〜2017年で、時期はほぼ重なっている。

そして「不便だから嫌」は、実は正しい検知だったと思う。

SMS の不便さと危険は、同じ原因から出ている。 アカウントの鍵が通信事業者の管理下にあるからだ。電波が入らないと使えない、番号を変えると失う、海外で受け取れない。全部「自分で制御できないものに依存している」ことの現れで、SIM スワップも同じ根から出ている。不便さは、依存が可視化された状態だった。

メールは許せて SMS は許せない理由も、感覚ではなく構造で説明できる。メールアドレスは移せるが電話番号は移せない。メールは自分で運用できるが SMS はできない。SIM の復旧は店舗の窓口で、免許証の偽造とアルバイト店員の判断が暗号鍵と同じ強度を持ってしまう。

SMS は、この設計で避けようとしてきたものを全部持っている。自分で制御できない、移せない、終端が他人の判断、そして奪われたことに気づけない。


二種類の信用

最後に、最初の問いに戻る。

「信用とは検証をやめた場所」と書いた。だが、それだけではなかった。

信用には「検証を委ねる」と「検証を可能にする」の二種類がある。

前者は相手を信じる。後者は相手を信じないまま使える。ライブラリをブラックボックスとして使う限り前者だが、根拠を読める形で受け取れば後者になる。同じものを、扱い方で別の種類の依存に変えられる。

AI から標準的な設計知識を取り出しながら、コードは自分で書く、というやり方も実はこれだ。信じる対象を減らしたのではなく、信じ方を変えていた。出力を信じるのではなく、根拠の構造を見て弾ける状態にしておく。実際この設計を詰める過程で、AI 側は何度も的外れな方向に行った。弾けたのは、根拠が明示されていたからだ。


線を引くということ

抽象的に「絶対安全はない」と言うのは簡単だ。でも実際に書いてみると、そこで何が起きているかがもっと具体的に分かる。

rename をアトミックだと信じたのは、POSIX の仕様とファイルシステムの実装がそう振る舞うからだ。Argon2id を選んだのは、今の GPU のコストで割に合わないからで、5年後は別の話だ。

書いた一行一行は、全部「ここまでは確かめない」という判断の記録だった。 安全性という性質を実装したのではなく、判断を積んでいた。

だから技術側から答えるなら、こうなる。

安全は、コードが持つ性質ではなく、コードを書いた人が引いた線の集合。

線がどこにあるか説明できる状態を、人は安全と呼んでいる。実際に破られないことではなく。

そして、もどかしさが止まる条件も分かった。「全部確かめた」ではなく、「壊れたときに何が起きるか書いてある」だ。

fsync を入れたのは、クラッシュしても壊れないと決めたから。セッションの epoch を持ったのは、乗っ取られた後に全部切れると決めたから。判断に迷ったら凍結側に倒すと決めたのは、判断できないときの動作を先に固定したから。どれも「壊れない」ではなく「壊れた後」の設計だ。

そこまで書けていれば、監視し続ける必要がなくなる。もどかしさが止まるのは、確実性が手に入ったときではなく、監視者としての自分を降りられたときだ。

線を引くことには、もう一つ意味がある。

責任の範囲を決めることは、責任の外側を決めることでもある。レジストラが落ちたらどうしようもない、と先に書いておけば、そのとき自分を責めずに済む。「防げたはずだ」という形の後悔から切り離せる。


閉じない問い

この問いは、たぶん完全には閉じない。

閉じないから何度も戻ってくるし、戻ってくるたびに前より具体的になる。「ログイン画面は安全か」から始まって、「ファイルへの書き込みは中断に耐えるか」まで解像度が上がった。次は運用中に、また別の粒度で戻ってくると思う。

この反復は、答えが出ていないことの証拠ではなく、設計を続けている人にだけ起きる現象だと思っている。作らない人はこの問いを持たない。持ち続けているのは、まだ責任の範囲を広げようとしているからだ。

だから、その居心地の悪さは解消しようとしなくていいのかもしれない。

pusyuuwanko

孤独が基本で、その状態が最も自由で心地いいと知っている青年。 君はなぜそんな事をするの?、見たくない、聞きたくない、となるような物を作り、問い続けるちょっとした創作者

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です