secure-reader: 保護モードで本を読ませる

examples/secure-reader — 読者がログインして、自分の持っている PDF の本を読むサイトです。本はファイルとしては送らず、保護モードで渡します。ブラウザは 10 ページずつ頼み、答えはどれも、ブラウザがその 1 回の要求のために作った鍵ペアに封印されています。区間を開いた後は、どちら側にもそれを開ける鍵が残りません。

cd examples/secure-reader
node web/build.mjs     # web/main.js と web/lib/worker.js をビルド(変換器なし)
go run .
open http://127.0.0.1:8084/    # alice / alice-pass は 2 冊とも、bob / bob-pass は 1 冊を持っている

サーバーがすること

main.go は起動時に -books の PDF(books/gen.mjs が作った 32 ページと 23 ページの見本の本 2 冊)をそれぞれ 1 度 BDF に変換し、表紙の画像とともに -cache に置きます。-cache の中身はファイルとしては配りません。公開するのは:

すべての応答に、サイト自身のスクリプトだけを許す Content-Security-Policy を付け、http.CrossOriginProtection が他のサイトからのフォームと区間の要求を断ります。

ブラウザがすること

web/main.ts は本を MiniViewer で { kind: "segments", url: "/segments/NAME" } として開きます。あとはレンダラの Worker(@bdfkit/core の SegmentLoader)がします。要求ごとに秘密鍵を取り出せない P-256 の鍵ペアを作り、要るページと持っている区間を添えて公開鍵を送り、答えを開いたら鍵を手放します。見えてきたページはまずその区間を取りに行き、3 ページ先の区間は先に取っておきます。サーバーがページを断ったとき(立ち読みの終わり、読むのが速すぎる)は、ページにそう出します。ビューアには find: false を渡しています。Worker が持っているのは届いたページだけなので、ビューアの検索バーで探せる範囲はブラウザ自身の検索と変わりません。そのためショートカットはブラウザのままにしてあります。本全体の検索は、ここではサーバーの仕事です。

この形にした理由

本全体を 1 つの鍵で 1 度だけ封印する(BDF のパスワードの暗号化がそうしています。spec §3.5)と、読者の手元に暗号文とその鍵が揃います。後でその鍵を手に入れた人は、送られたものを全部開けます。ここでは区間ごとに、どちらも捨てられる 2 つの鍵の ECDH で鍵を合意するので、後でサーバーの TLS の鍵、読者のパスワード、セッションの Cookie が漏れても、記録された通信は封印されたままです。サーバーが決められるのは、だれがどのページを、どれだけの速さで読むかです。

費用は小さく、区間 1 つの封印は本の輪郭と鍵の合意を含めて 1 ミリ秒を大きく下回ります(design.md §3.30)。複数のページが使うフォントや画像は、それを最初に使う区間で 1 度だけ送ります。あきらめるのは CDN の共有キャッシュ(答えはどれも 1 人の読者に封印される)と、オフラインの閲覧(端末に鍵を残すことになる)です。

本を読んでよい読者が画面に見えるものを写すことは止められません。また、サーバーは本を平文で持っています。フォーマットの側は spec §3.6 です。