すべての記事

約10分で読めます

CardanoWall はブラウザで鍵をどこに保管するのか

ブラウザでは、CardanoWall は解錠した鍵をセッションメモリに保持し、IndexedDB には暗号化された保管庫の暗号文だけを書き込みます。平文の Identity Seed や秘密鍵を書き込むことはありません。

ブラウザで CardanoWall を解錠すると、解錠されたシードと、そこから導出された秘密鍵はセッションメモリに置かれます。これは、ロックしたりサインアウトしたり、タブを閉じたりすると消える通常のアプリケーションメモリです。永続的なブラウザストレージ(IndexedDB、sessionStorage)が保持するのは、暗号化された保管庫の暗号文と、秘密でないメタデータだけです。平文の Identity Seed や、導出された秘密鍵がそこに書き込まれることはありません。

この区別こそが核心です。ローカルで署名や復号を行うには、ブラウザが鍵素材を保持する必要があります。それがなければ暗号処理そのものが成り立たないからです。安全性をめぐる問いは「ブラウザが鍵に触れてよいか」ではありません。触れざるを得ないのです。問うべきは、その鍵がどこに置かれるか、そして何がディスクに書き込まれるかです。CardanoWall のウェブモデルは、リロードを生き延びるものが秘密ではなく暗号文になるように設計されています。

アイデンティティの解錠中、ブラウザには何が必要なのか

必要なのは、実際に行おうとしている作業のための秘密鍵素材であって、セッションそのものより長く残るものは何ひとつ必要ありません。

アイデンティティを解錠したあと、アプリは次のような処理を行う場合があります。

  • Label 309 レコードに署名する
  • 自分宛ての封印付きレコードを復号する
  • 受信した封印付きレコードを試行復号(trial-decrypt)し、自分の受信トレイ向けのものを見つける
  • パスキーを追加・削除したあとに保管庫を再暗号化する
  • 明示的に要求したときにシードを表示する
  • ローカルの暗号化キャッシュを再構築する

これらはいずれも、公開鍵だけでは実行できません。どの操作にも、Identity Seed から導出された秘密素材が必要です。設計上、その素材はセッションメモリに保持され、ロック時やサインアウト時に消去されます。ただし、これはベストエフォートでの消去です。その理由は後述します。

ここでいうセッションメモリとは何か

セッションメモリとは、解錠セッションが終わるとアプリが破棄する、一時的でプロセス内のアプリケーションメモリです。

CardanoWall のブラウザアプリでは、稼働中のシードと、そこから導出された鍵は、通常の UI 状態の外側にある内部のメモリマップに保持されます。リアクティブな UI 状態が保持するのは、秘密でない事実だけです。どのアイデンティティが解錠されているか、いつ解錠されたか、セットアップ中に画面に表示されているパスキー登録メタデータはどれか、といった情報です。秘密のバイト列がこのリアクティブな状態に入り込むことはありません。

この分離が重要なのは、通常の UI 状態が、アプリの中でもっとも検査・シリアライズ・永続化されやすく、あるいはログを出力するコンポーネントに誤って渡されやすい部分だからです。秘密素材には、より小さく、意図的に列挙された表面がふさわしいのです。署名クロージャ、復号鍵、シード表示の例外経路、保管庫の再構築パスといった、秘密のバイト列を渡し得るアプリ内のすべての箇所は 1 つのファイルにまとめられており、それぞれが稼働中のバッファそのものではなく防御的なコピーを返します。

解錠中はブラウザが依然として秘密を保持しています。それを通常のアプリデータとして扱わない、というだけのことです。

IndexedDB には何が書き込まれるのか

書き込まれるのは、暗号化された保管庫の暗号文です。サーバーが保管しているのと同じバイト列であり、その中身であるシードではありません。

IndexedDB は、暗号化されたアイデンティティ保管庫のローカルキャッシュとして使われます。アカウントごとに 1 行で、サーバーが保持しているのとまったく同じ age 暗号化済みの暗号文を保持します。これをキャッシュしておくことで、リロード後もパスキーを 1 回タップするだけでアプリを復元でき、保管庫を取得し直すための往復通信が不要になります。

このローカルデータベースを読み取っただけの攻撃者に見えるのは、ご自分のパスキー宛てに暗号化された保管庫のバイト列であって、平文のシードではありません。とはいえ、このキャッシュは依然として機微なサービスデータなので、アプリはサインアウト時、アカウント削除時、パスキー変更時にこれを消去します。しかしこれはアイデンティティそのものではありません。施錠された箱であり、その鍵はお使いの認証器の中だけに存在します。(この箱がどのように封印されるかは、CardanoWall がアイデンティティをどう保管するかパスキーがアイデンティティ保管庫をどう守るかをご覧ください。)

これらの書き込みは、公共コンピューターモードのとき、およびデバイスを記憶しないことを選んだときには、いっさい行われません。

sessionStorage には何が入るのか

入るのは、秘密でない登録メタデータだけです。鍵素材が入ることはありません。

アイデンティティ作成中、アプリはパスキーの登録メタデータを sessionStorage にミラーします。これは、セットアップ途中で誤ってリロードしても、表示中の状態が消えないようにするためです。このメタデータは構造上、秘密ではありません。クレデンシャル ID、公開鍵、トランスポート、デバイス種別やバックアップのフラグなど、攻撃者が手に入れても何も得られない情報です。

sessionStorage から明示的に排除されているのは、次のものです。

  • Identity Seed
  • 導出された秘密鍵
  • WebAuthn PRF(pseudo-random function)の出力。保管庫を解錠するためにパスキーが返す秘密
  • 保管庫の平文
  • 復号された封印コンテンツ

線引きは単純です。インターフェイスの連続性はリロードをまたいで保持してよいが、アイデンティティの秘密は保持すべきではない、ということです。

そもそもブラウザストレージに置いてはならないものは何か

平文のアイデンティティ素材です。どのストアであっても置きません。

次のものは、localStorage、sessionStorage、IndexedDB、Cookie、ログ、通常のアプリ状態のいずれからも排除されています。

  • Identity Seed
  • Ed25519 署名秘密鍵
  • X25519 受信秘密鍵
  • ハイブリッドなポスト量子の受信秘密(オプションの age1pqc... アドレスの背後にあるシード)
  • WebAuthn PRF の出力
  • 保管庫の平文
  • 復号された封印コンテンツ(ただし、明確に管理できる暗号化ローカルキャッシュへ明示的に保存した場合を除く)

理由は、設計全体を貫くものと同じです。秘密が書き込まれる場所が多いほど、その削除について考えるのが難しくなり、侵害された際に読み取られ得る表面も大きくなります。

公共コンピューターモードとは何か

これは、共有デバイス向けの明示的なモードです。オンにしている間は、アイデンティティに関するものはブラウザストレージにいっさい書き込まれません。

オンにすると、アプリはアイデンティティに関するあらゆる永続化パスをスキップします。PIN 保管庫も、IndexedDB の保管庫キャッシュも、バージョンピンも、sessionStorage の登録ミラーもありません。解錠された鍵はセッションメモリだけに置かれ、そのタブ内のアプリ内ナビゲーションでは生き残りますが、タブを閉じたりリロードしたりすると消えます。このトグル自体があえてメモリ上のみにしてあるのには理由があります。「いま公共コンピューターを使っている」という状態を永続化すれば、それ自体がブラウザストレージへの書き込みになってしまうからです。共有マシンは、つねに安全な既定値、つまり再度確認する状態で開き直すべきです。図書館のパソコン、借りたノートパソコン、カンファレンスのマシン、報道機関のキオスク端末など、ご自分が管理していないものでお使いください。

このモードがしないのは、信頼できないデバイスを安全にすることです。シードを入力したりアイデンティティを解錠したりする時点でそのマシンがすでに侵害されていれば、メモリ上の秘密はなお観測され得ます。このモードが減らすのは、離席したあとに残るものです。稼働中のローカルマルウェアを無力化するわけではありません。詳しい使い方は公共コンピューターモードで解説しています。

JavaScript におけるゼロ化とは何を意味するのか

それは、秘密のバイト配列を、アプリが使い終わるとすぐに上書きする(ゼロで埋める)ことを意味します。

CardanoWall は、アイデンティティをロックしたときやサインアウトしたときに、Uint8Array の鍵バッファをゼロ化します。ガベージコレクタが回収してくれるのを待って期待するのではなく、自ら上書きするのです。これは良い習慣であり、やる価値があります。

ただし、JavaScript は秘密を扱うために堅牢化された環境ではありません。そう正直に言うべきです。文字列はイミュータブルなので、一度でも文字列になった秘密はその場で消去できません。ガベージコレクションのタイミングはアプリの制御下にありません。エンジンは内部でメモリをコピーすることがあります。開発者ツール、拡張機能、侵害されたオリジンは、リスクモデルそのものを一変させます。

ですから、ここでのゼロ化はベストエフォートの手段であって、消去を保証するものではありません。はぐれたコピーが残る時間枠を有意に縮めはしますが、バイト列があらゆる場所から消えたと約束するものではありません。

解錠中にページが侵害されたらどうなるのか

その場合、アイデンティティは本当に危険にさらされます。これは、ブラウザ上の暗号処理すべてにおいてもっとも厳しい境界です。

ページコンテンツにアクセスできる悪意あるブラウザ拡張機能、侵害されたデバイス、あるいは解錠中のセッション中に動作する深刻なクロスサイトスクリプティング(XSS)のバグは、メモリから秘密を読み取ったり、意図しない署名や復号をアプリに行わせたりできる場合があります。これを完全に排除できるウェブアプリはありません。ネットワーク経由で届いたコードが鍵を扱う以上、同じオリジンで動作する他のあらゆるものにさらされるからです。

CardanoWall は、この時間枠を縮めるために多層防御に頼っています。スクリプトをアプリ自身のオリジンに限定する厳格な Content Security Policy(インラインスクリプトなし、eval なし、サードパーティスクリプトの読み込みもいっさいなし)に加えて、エッジですべてのレスポンスに付与されるセキュリティヘッダー、あえて小さく保たれた秘密の表面、平文を永続化しないこと、そして解錠と復号をユーザーの明示的な操作時にのみ行うこと(タブにフォーカスが当たったときに自動で行うことはありません)です。これらは可能性と影響範囲を減らします。しかし、解錠されたオリジン内の悪意あるスクリプトを無害化するわけではありません。それは、ブラウザモデルが受け入れる、もっとも影響の大きい脅威であり続けます。

もっとも機微なアイデンティティについては、正しい答えは鍵素材を共有のウェブ表面から外に移すことです。専用の信頼できるデバイスに保管し、秘密がブラウザにいっさい触れないワークフローを選んでください。たとえば、自動化の中で使う CLIや、ウェブのオリジンではなくローカルの Rust コアに鍵を保持する CardanoWall Desktop です。(より広い原則については、鍵がデバイスから出ない理由をご覧ください。)

では実際に何をすればよいのか

ブラウザモデルは意図して使い、作業の機微さに応じて対策を合わせてください。

日常的な利用には、次のとおりです。

  • Identity Seed を安全な場所に保存する。これが本当のバックアップです
  • 日々の解錠のためにパスキーを追加する
  • 作業が終わったらロックするかサインアウトする
  • 「このデバイスを記憶する」は信頼できるマシンでのみ使う
  • ブラウザと OS を最新に保つ
  • リスクの高いブラウザ拡張機能は避ける
  • 共有デバイスでは公共コンピューターモードをオンにする

機微な作業では、さらに次のとおりです。

  • 専用のブラウザプロファイル、できれば専用のデバイスを用意する
  • 借りたマシンでは解錠しない
  • 解錠の要素としてハードウェアセキュリティキーを使う
  • リスクの高いアイデンティティと通常のアイデンティティを分離する
  • 封印付きレコードには注意する。復号できる受信者は、あとで平文を漏らすこともできます

要点

CardanoWall のブラウザアプリは、アイデンティティの解錠中に秘密鍵を必要とするため、それを永続ストレージではなくセッションメモリに保持します。IndexedDB がキャッシュするのは、暗号化された保管庫の暗号文だけです。sessionStorage が保持するのは、秘密でないセットアップメタデータだけです。Identity Seed と秘密鍵は、通常のブラウザデータとして書き込まれることはありません。

ホスト型の保管庫モデルと、ブラウザストレージのモデルは互いを補強し合います。サーバーは自身では復号できない暗号文を保持し、ブラウザはシードを残さないよう懸命に努めます。どちらの主張も、すでに侵害されたデバイスに対する保証ではありません。その限界を明確にすることが、これを真剣に受け止めることの一部です。サービスが何を観測でき、何を観測できないかの全体像については、CardanoWall に見えるものをご覧ください。

securitybrowser-securityidentity