暗号化されたノート(Encrypted Notes)
Encrypted Notes は、短いテキスト形式で秘密の情報を作成・保存する試験的なアプリケーションです。ユーザーは Internet Identity で認証された、自動的に同期される複数のデバイスから自分のノートにアクセスすることができます。アプリのフロントエンドがエンドツーエンドで暗号化するため、ユーザーはアプリのバックエンドを信頼する必要がありません。
こちらの IC 上にデプロイされている Dapp から試すことが出来ます。
アイディア
純粋に IC 上で動作する簡素な(しかし簡素すぎない)アプリケーションの例を示したいと思います。この例は IC の ウェブ配信機能 と ストレージ機能 に依存しています。このアプリの例で次の 2 つの主要な機能に焦点を当てます。(1)クライアントサイドでの エンドツーエンドの暗号化 、(2) マルチユーザー 、 マルチデバイス のサポート。
このようなアプリの開発プラットフォームとしての IC の可能性を示すために、2 種類の Canister Development Kits ( CDKs )を用いて実装を行いました。Motoko CDK は Motoko 言語を使って Actor ベースの Dapp を実装するためのものです。Rust CDK は Rust 言語で実装されたアプリを提供します。いずれも Canister を WebAssembly ファイルにコンパイルし、 IC 上にデプロイします。
アプローチ
Encrypted Notes の基本機能は 2 つの主要コンポーネントで構成されています。
第一に、 IC-Notes という Dapp のコード(非暗号化) を再利用しました。特に IC-Notes はユーザー認証のために Internet Identity ( II ) Canister に依存しており、このアプローチは Encrypted Notes にも継承されています。開発目的では II Canister のローカルインスタンス( Encrypted Notes のローカルインスタンスと共に)をデプロイします。Encrypted Notes をメインネットにデプロイする場合、現実世界の II インスタンスが認証に使用されます。
第二に、クライアントサイドでノートの内容のエンドツーエンドの暗号化を可能にしました。これは、 IC-Vault という別の既存のアプリからソリューションを借用したものです。私たちの Encrypted Notes アプリは IC-Vault のアプローチを踏襲し、複数のデバイスの管理をサポートしています。
このドキュメントで論じる Canister の文脈では、デバイスは必ずしも独立した物理デバイスではなく論理的なインスタンスデバイスです。つまり、独自のローカルデータストレージを持つ Web ブラウザなどです。例えば、同じノートパソコン上で動作する 2 つの Web ブラウザを独立した 2 つのデバイスと見なし、これらのブラウザは独自の暗号鍵を生成します。これに対し、II Canister はハードウェアが生成する暗号鍵に依存し、ハードウェアデバイスのみを識別します。
ユーザーごとに複数のデバイスをサポートするために、IC-Vault はデバイスマネージャーとユーザーに結びついたすべてのデバイス間でデバイス固有の鍵を安全に同期させる Canister を採用しています。このドキュメントの残りの部分では、メイン Canister の一部として同様の方法でデバイスマネージャーを実装している Encrypted Notes Canister に焦点を当てます。
詳細やユーザーストーリーは README ファイル を参照して下さい。
ノートマネージメント
ユーザーはフロントエンドで II にリンクされ、API クエリやアップデートを呼び出すために使用できる Principal を取得します。
内部的には、
Principal → [Notes]
という形式のマップと、counter
を格納しています。counter
には、Canister が作成した(すべての Principal にわたる)ノートの数が格納されます。create
メソッドは、Principal のエントリがすでに存在する場合にはそのエントリに、そうでなければ Principal を新たにマップに追加し、note_id == counter
であるノートを追加した後に、counter
をインクリメントします。update
メソッドは、呼び出し元の Principal と指定されたnote_id
に対応するノートを取り出し、与えられたtext
で置き換えます (このtext
はフロントエンドによって暗号化されていると仮定します)。delete
メソッドは、指定されたnote_id
を持つノートをマップから探し出し削除します。ノート ID が常にグローバルに一意であることを保証するために、counter
を減少させないようにします。
暗号化
ノートの暗号化は完全にクライアントサイドで行われます。しかし、このサンプルアプリは、悪意のあるノードプロバイダによるデータ漏洩攻撃の可能性からはまだ保護されていません。例えば、攻撃者は特定のユーザーが何冊のノートを持っているかや、ユーザーの活動統計などを推論することができます。したがって、このアプリのコードやパターンを使用する前に、免責条項をよく読んでください。
このアプリの定義では、デバイスは必ずしも独立した物理デバイスではなく、単に独立したローカルストレージを持つ web ブラウザのインスタンスであることを思い出してください。
Dapp は3種類の違う鍵を利用します。
対称型 AES-GCM 秘密鍵 : 指定された Principal のノートを暗号化するために使用されます。Principal のノートはこの秘密鍵で暗号化されて Encrypted notes Canister に保存されます。したがって、アプリのフロントエンドはこのユーザーのノートを復号したり、 Encrypted Notes Canister に暗号化したノートを格納するためにこの秘密鍵を知っている必要があります。
デバイス RSA-OAEP 公開鍵 : Principal の対称型 AES 秘密鍵 を暗号化するために使用されます。暗号化された秘密鍵は Principal に登録されたデバイスそれぞれに対して Canister に格納されます。そのデバイスを使用する Principal が異なると同じ鍵が使用されます。
デバイス RSA-OAEP 秘密鍵 :Encrypted notes Canister に格納されている、指定された Principal の対称型 AES 秘密鍵 を復号するために使用されます。フロントエンドが秘密鍵を復号すると、 Encrypted Notes Canister に保存されているノートを復号するためにこの鍵を使用することができます。
フォームのマップを保存します。
Principal → (DeviceAlias → PublicKey,
DeviceAlias → CipherText)このマップは次に説明するように、ユーザーデバイスを管理するために使用されます。
デバイスを登録するために、フロントエンドはデバイス・エイリアス、公開鍵、秘密鍵(ローカルストレージに保持)を生成します。
デバイスを追加すると、以下が行われます。
デバイスの登録: この ID がすでに登録されていると、このデバイスの
alias
とpublickey
だけが Encrypted Notes Canister に追加されます。デバイスの同期: 同期していないデバイスがこの II のすべての同期していないデバイスのリストを取得すると、同期していないデバイスのそれぞれの公開鍵で対称型 AES 秘密鍵 を暗号化します。その後、同期していないデバイスは暗号化された対称型 AES 秘密鍵 を取得して復号し、それを使って Encrypted Notes Canister に保存されている既存のノートを復号します。
一度 II で認証すると、以下が行われます。
この ID が未登録の場合、フロントエンドは対称型 AES 秘密鍵 を生成し、それを自身の公開鍵で暗号化します。そして
seed(publickey, ciphertext)
を呼び出し、その暗号文とそれに関連するpublickey
をマップに追加します。ユーザーが後続のデバイスを登録したい場合、フロントエンドは
register_device
を呼び出して、そのデバイスのalias
とpublickey
を渡します。次に、フロントエンドは登録する必要があるすべてのデバイスに対してsubmit_ciphertexts([publickey, ciphertext])
を呼び出します。これにより、登録されたデバイスはユーザーノートを暗号・復号化するための AES 鍵を復号し引き出すことができるようになります。