Open7

The Messaging Layer Security (MLS) Protocol

抄録

メッセージングアプリケーションでは、エンド・ツー・エンドのセキュリティメカニズムを利用することが多くなっています。これにより、メッセージは通信を行うエンドポイントのみがアクセス可能であり、メッセージの配信に関わるサーバーはアクセスできないようになっています。このようなセキュリティ機能を実現するための鍵の確立は、グループチャットのように、同時に2つ以上のクライアントがオンラインでない場合には困難です。本ドキュメントでは、2人から数千人までのグループに対して、前方秘匿性と妥協後のセキュリティを備えた効率的な非同期グループ鍵確立プロトコルを規定しています。

1. 序章

暗号化されたメッセージを送り合いたいユーザーのグループは、共有された対称暗号鍵を導き出す方法が必要です。2パーティの場合、この問題は徹底的に研究されており、ダブルラチェットが一般的な解決策として登場している [doubleratchet] [signal]。Double Ratchetを実装したチャネルは、細かい前進秘密と妥協後のセキュリティを備えていますが、低帯域幅のネットワークで多用するには十分効率的です。

2人以上のグループの場合、一般的な戦略は、既存の共有対称チャンネルで対称的な「送信者」鍵を一方的にブロードキャストし、各メンバーが自分の送信者鍵で暗号化したメッセージをグループに送信するというものです。残念なことに、この方法は、個々のメッセージをペアで送信するよりも効率が良く、(ハッシュ・ラチェットを追加することで)前方秘匿性が得られますが、送信者鍵を使って事後セキュリティを実現するのは困難です。送信者鍵を知った敵対者は、そのメンバーのメッセージを無期限かつ受動的に盗聴することができます。新しい送信者鍵を生成して配布することで、その送信者に関するポスト・コンプロマイズ・セキュリティを実現することができます。しかし、そのためには、グループの規模に応じて線形に変化する計算・通信資源が必要になります。

この文書では、木構造をベースにしたプロトコルにより、前方秘匿性と妥協後のセキュリティを備えた非同期グループキーイングを実現しています。このプロトコルは、「非同期ラチェットツリー」[art]の研究に基づいており、木構造のための非同期キーカプセル化メカニズムを使用しています。この機構により、グループのメンバーは、グループサイズの対数としてスケールするコストで、共有鍵の導出と更新を行うことができます。

2. 用語解説

本文書中のキーワード「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「NOT RECOMMENDED」、「MAY」、「OPTIONAL」は、以下に示すように、それらがすべて大文字で出現する場合にのみ、BCP 14 [RFC2119] [RFC8174]に記述されているように解釈されるべきである。

クライアント(Client): このプロトコルを使用して、他のクライアントと共有の暗号状態を確立するエージェント。クライアントは、それが保持する暗号鍵によって定義される。

グループ: 暗号状態を共有するクライアントの集合体。

Member(メンバー): グループの共有状態に含まれ、グループの秘密にアクセスできるクライアント。

鍵パッケージ: クライアントの身元と能力を記述し、そのクライアントに暗号化するために使用できるハイブリッド公開鍵暗号化(HPKE [I-D.irtf-cfrg-hpke])公開鍵を含む署名済みオブジェクト。

初期化鍵(InitKey): クライアントによって事前に発行された鍵パッケージで、他のクライアントがそのクライアントを新しいグループに紹介するために使用できる。

Signature Key(署名鍵): メッセージの送信者を認証するために使用される署名鍵のペア。

ツリー計算に特有の用語についてはセクション5で説明する。

プロトコルメッセージの構造を記述するために、TLSプレゼンテーション言語 [RFC8446]を使用する。

3. 基本的な前提条件

このプロトコルは、[I-D.ietf-mls-architecture]に記述されているように、サービスプロバイダ(SP)のコンテキストで実行するように設計されています。特に、SPは以下のサービスを提供すると仮定する。

  • クライアントがグループ内のプロトコルメッセージを認証することを可能にする署名鍵プロバイダ。
  • グループごとに、グループの全メンバーにメッセージを中継するブロードキャストチャネル。ほとんどの場合、このチャネルは、すべての参加者に同じ順番でメッセージを配信すると仮定しています。(詳細はセクション13を参照のこと)。
  • ディレクトリ クライアントが鍵パッケージを公開したり、他の参加者のために鍵パッケージを ダウンロードしたりできるディレクトリ。

4. プロトコルの概要

このプロトコルの目的は、クライアントのグループが機密性のある認証されたメッセージを交換できるようにすることです。このプロトコルは、メンバーだけが知っている一連の秘密と鍵を導き出すことで実現しています。これらの秘密と鍵は、ネットワーク上の敵に対して秘密であり、メンバーの危殆化に対して前方秘匿性と事後秘匿性を持つ必要があります。

ここでは、各クライアントが保存している情報を、公開データと秘密データの両方を含む「状態」と表現します。初期状態は、自分自身だけを含むグループであるグループクリエーターによって設定される。次に、グループ作成者は、初期メンバーの各クライアントに対してAddプロポーザルを送信し、続いて、すべてのAddをグループの状態に組み込むCommitメッセージを送信します。最後に、グループ作成者は Commit に対応する Welcome メッセージを生成し、これを新しいメンバー全員に直接送信します。メンバーは、このメッセージに含まれる情報を使用して、自分のグループの状態を設定し、共有秘密を導き出すことができます。メンバーは、妥協後のセキュリティのために Commit メッセージを交換し、新しいメンバーを追加したり、既存のメンバーを削除したりします。これらのメッセージは、前任者と因果関係で結ばれた新しい共有秘密を生成し、状態の論理的なDAG(Directed Acyclic Graph)を形成します。

ここでは、プロトコルのアルゴリズムを説明します。各アルゴリズムは、(i)クライアントがどのように操作を行うか、(ii)他のクライアントがそれに基づいてどのように状態を更新するか、の両方を規定しています。

グループのライフサイクルには、大きく分けて3つの操作があります。

  • 現在のメンバーが始めたメンバーを追加すること。
  • メンバーのリーフシークレットを更新すること。
  • メンバーを削除する。

これらの操作は、それぞれ対応するタイプのメッセージ(Add / Update / Remove)を送信することで「提案」されます。しかし、Commitメッセージが送信されてグループに新しいエントロピーが与えられるまで、グループの状態は変更されません。このセクションでは、各プロポーザルがすぐにコミットされる様子を示していますが、より高度な導入ケースでは、アプリケーションが複数のプロポーザルを集めてから、それらをすべて一度にコミットすることもあります。

グループの初期化の前に、クライアントはサービス・プロバイダが提供するディレクトリにInitKeysを(KeyPackageオブジェクトとして)発行する。

                                                               Group
A                B                C            Directory       Channel
|                |                |                |              |
| KeyPackageA    |                |                |              |
|------------------------------------------------->|              |
|                |                |                |              |
|                | KeyPackageB    |                |              |
|                |-------------------------------->|              |
|                |                |                |              |
|                |                | KeyPackageC    |              |
|                |                |--------------->|              |
|                |                |                |              |

クライアントAは、BとCとの間でグループを構築しようとするとき、まず自分だけを含むグループの状態を初期化し、BとCのためにKeyPackageをダウンロードする。また、Welcome メッセージを生成し、これを新しいメンバーに直接送信します(グループに送信する必要はありません)。Aは、サーバーからのCommitメッセージを受信した後にのみ、新メンバーの追加を反映して状態を更新します。

Welcome メッセージを受信すると、新メンバーは新しいメッセージを読んだり、グループに送信したりすることができるようになります。クライアントがグループに参加する前に受け取ったメッセージは無視されます。

                                                               Group
A              B              C          Directory            Channel
|              |              |              |                   |
|         KeyPackageB, KeyPackageC           |                   |
|<-------------------------------------------|                   |
|state.init()  |              |              |                   |
|              |              |              |                   |
|              |              |              | Add(A->AB)        |
|              |              |              | Commit(Add)       |
|--------------------------------------------------------------->|
|              |              |              |                   |
|  Welcome(B)  |              |              |                   |
|------------->|state.init()  |              |                   |
|              |              |              |                   |
|              |              |              | Add(A->AB)        |
|              |              |              | Commit(Add)       |
|<---------------------------------------------------------------|
|state.add(B)  |<------------------------------------------------|
|              |state.join()  |              |                   |
|              |              |              |                   |
|              |              |              | Add(AB->ABC)      |
|              |              |              | Commit(Add)       |
|--------------------------------------------------------------->|
|              |              |              |                   |
|              |  Welcome(C)  |              |                   |
|---------------------------->|state.init()  |                   |
|              |              |              |                   |
|              |              |              | Add(AB->ABC)      |
|              |              |              | Commit(Add)       |
|<---------------------------------------------------------------|
|state.add(C)  |<------------------------------------------------|
|              |state.add(C)  |<---------------------------------|
|              |              |state.join()  |                   |

その後のグループメンバーの追加も同様に行われます。グループのメンバーは誰でも、新しいクライアント用のKeyPackageをダウンロードし、現在のグループがその状態を更新するために使用できるAddメッセージと、新しいクライアントがその状態を初期化してグループに参加するために使用できるWelcomeメッセージをブロードキャストすることができる。

メッセージの前方秘匿性と妥協後のセキュリティを確保するために、各メンバーは定期的にリーフシークレットを更新します。どのメンバーも、新しい KeyPackage を生成し、Update メッセージに続いて Commit メッセージを送信することで、いつでもこの情報を更新することができます。すべてのメンバーが両方の処理を行った時点で、グループの秘密は、送信者の以前のリーフシークレットを侵害した攻撃者にはわからなくなります。

グループが活動している間は、一定の間隔でUpdateメッセージを送信し、更新しないメンバーは最終的にグループから削除する必要があります。Updateの適切な間隔は、アプリケーションに任されています。

                                                          Group
A              B     ...      Z          Directory        Channel
|              |              |              |              |
|              | Update(B)    |              |              |
|              |------------------------------------------->|
| Commit(Upd)  |              |              |              |
|---------------------------------------------------------->|
|              |              |              |              |
|              |              |              | Update(B)    |
|              |              |              | Commit(Upd)  |
|<----------------------------------------------------------|
|state.upd(B)  |<-------------------------------------------|
|              |state.upd(B)  |<----------------------------|
|              |              |state.upd(B)  |              |
|              |              |              |              |

グループからのメンバーの削除も同様の方法で行われます。グループのメンバーは誰でも、Removeプロポーザルに続いてCommitメッセージを送信することができます。このメッセージは、削除されたメンバーを除くすべての人が知っているグループの状態に、新たなエントロピーを追加します。グループは、この基本的なメカニズムに加えて、アクセス制御ポリシーを適用することができます。

                                                          Group
A              B     ...      Z          Directory       Channel
|              |              |              |              |
|              |              | Remove(B)    |              |
|              |              | Commit(Rem)  |              |
|              |              |---------------------------->|
|              |              |              |              |
|              |              |              | Remove(B)    |
|              |              |              | Commit(Rem)  |
|<----------------------------------------------------------|
|state.rem(B)  |              |<----------------------------|
|              |              |state.rem(B)  |              |
|              |              |              |              |
|              |              |              |              |

5. ラチェットツリー

このプロトコルでは、クライアントのグループ間の共有秘密を導き出すために「ラチェットツリー」を使用します。

5.1. ツリー計算の用語

ツリーはノードで構成されています。ノードは、子供を持たない場合は葉であり、そうでない場合は親である。我々の木のすべての親は、正確に2つの子供、左の子供と右の子供を持つことに注意してください。ノードは、親を持たない場合は木の根であり、子と親の両方を持つ場合は中間である。あるノードの子孫とは、そのノード、その子、およびその子の子孫であり、そのノードが木の根の子孫である場合、その木にはノードが含まれると言う。ノードは、同じ親を共有していれば兄弟である。

木のサブツリーとは、サブツリーの頭であるノードの子孫からなる木のことである。ツリーまたはサブツリーのサイズは、含まれるリーフノードの数です。与えられた親ノードに対して、その左サブツリーは、その左の子をヘッドとするサブツリー(それぞれ右サブツリー)である。

このプロトコルで使用する木はすべて、左バランスの二分木です。二分木は、そのサイズが2の累乗であり、その木のどの親ノードについても、その左サブツリーと右サブツリーのサイズが同じである場合、完全(およびバランス)である。

二分木が左バランス型であるのは、すべての親について、その親がバランス型であるか、その親の左サブツリーが、その親のサブツリーに存在する葉から構成される最大のフルサブツリーである場合です。n個の項目からなるリストが与えられたとき、これらの項目を葉とする左バランスの二分木構造が一意に存在する。

(左バランスの二分木は、証明書透過プロトコル[I-D.ietf-trans-rfc6962-bis]のMerkle木に使用されているのと同じ構造であることに注意してください)。

ルートのダイレクトパスは空のリストであり、他のノードのダイレクトパスは、そのノードの親と親のダイレクトパスを連結したものである。ノードのコパスは,そのノードの兄弟と,ルートを除くそのノードの直接経路にあるすべてのノードの兄弟のリストを連結したものである。

例えば、以下のツリーの場合。

  • Cのダイレクトパスは(CD, ABCD, ABCDEFG)であり
  • Cのコパスは(D, AB, EFG)である。
            ABCDEFG
           /      \
          /        \
         /          \
     ABCD            EFG
    /    \          /  \
   /      \        /    \
  AB      CD      EF    |
 / \     / \     / \    |
A   B   C   D   E   F   G

                    1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2

ツリーの各ノードには,0から始まり左から右に向かうノードインデックスが割り当てられている.ノードは,偶数のインデックスを持つ場合に限り,リーフノードとなる.上のツリーのノードのノードインデックスは以下の通りである。

  • 0 = A
  • 1 = AB
  • 2 = B
  • 3 = ABCD
  • 4 = C
  • 5 = CD
  • 6 = D
  • 7 = ABCDEFG
  • 8 = E
  • 9 = EF
  • 10 = F
  • 11 = EFG
  • 12 = G

プロトコルメッセージはツリー内のリーフを参照するだけでよいので、ツリーのリーフはリーフインデックスを使って別々にインデックスされます。ノードと同様に、リーフにも左から右に番号が振られます。リーフインデックスkのノードはk番目のリーフとも呼ばれます。上記の番号付けでは、ノードが偶数のノード・インデックスを持つ場合にのみ、ノードはリーフ・ノードであり、リーフ・ノードのリーフ・インデックスはそのノード・インデックスの半分であることに注意されたい。上記ツリーのリーフインデックスは以下の通りである。

  • 0 = A
  • 1 = B
  • 2 = C
  • 3 = D
  • 4 = E
  • 5 = F
  • 6 = G

5.2. ラチェットツリーノード

ラチェットツリーの特定のインスタンスは、HPKEのインスタンスを定義するのと同じパラメータによって定義される。

  • 鍵カプセル化メカニズム(KEM)であり、対称秘密からKEM用の鍵ペアを作成するDeriveKeyPair関数を含む。
  • 抽出・展開関数を含むキー導出関数(KDF)
  • AEAD暗号化方式

ラチェット・ツリーの各ノードには、最大 5 つの値が含まれます。

  • プライベート・キー (メンバーのダイレクト・パス内のみ、以下を参照)
  • 公開鍵
  • 非マージ」リーフのリーフインデックスの順序付きリスト (セクション5.3参照)
  • クレデンシャル (リーフノードのみ)
  • ノードが最後に変更された時の、ノードの親に関するある種の情報のハッシュ(セクション7.4参照)。

これらの値が存在しなければならない、あるいは存在してはならない条件については、セクション5.3で説明しています。

ツリー内のノードは空白であることもあり、そのノードに値が存在しないことを示します。ノードの解像度は、そのノードのすべての非ブランクの子孫を総称してカバーする非ブランクノードの順序付きリストです。

  • ブランクではないノードの解決は、ノード自体と、それに続くマージされていない葉のリスト(もしあれば)で構成されます。
  • 空のリーフノードの解像度は、空のリスト
  • 空の中間ノードの解決は,左の子の解決と右の子の解決を順番に連結した結果です.

例えば、以下のツリーを考えてみましょう。

      _
    /   \
   /     \
  _       CD[C]
 / \     / \
A   _   C   D

0 1 2 3 4 5 6

このツリーでは、上記のルールがすべてプレイ中であることがわかります。

  • ノード5の解決策はリスト[CD, C]です。
  • ノード2の解像度は空リスト[]です。
  • ノード3の分解能は、リスト[A,CD,C]である。

ノードが空白であるか埋められているかにかかわらず、すべてのノードは、そのノード以下のサブツリーの内容を要約する対応するハッシュを持っています。これらのハッシュの計算規則は7.5節で説明します。

5.3. ラチェットツリーのビュー

一般的に、各参加者は、すべてのノードの公開鍵と、リーフノードに関連する信任状を 含む、グループのラチェットツリーの公開状態について、完全かつ最新のビューを維持して いると想定している。

MLSグループの参加者は、ツリーのすべてのノードに関連する秘密鍵を知らない。代わりに、各メンバーはツリーのリーフに割り当てられ、そのリーフが知っているプライベー ト鍵のサブセットを決定する。そのリーフに格納されるクレデンシャルは、メンバーが提供するものである。

特に MLS は、ツリーの不変性を維持するような方法で、メンバーのツリーの見方を維持する。

ツリー内のあるノードの秘密鍵は、そのメンバーのリーフがそのノードの子孫である場合にのみ、グループのメンバーに知られます。

言い換えれば、あるノードが空白でなければ、そのノードは公開鍵を持っていることになる。対応する秘密鍵は、そのノードの下の葉を占めるメンバーのみが知っている。

逆の意味合いは真ではありません。あるメンバーは、自分が下にいるすべての中間ノードの秘密鍵を知らないかもしれません。このようなメンバーは、マージされていないリーフを持っています。中間ノードを暗号化するには、そのノードの公開鍵と、その下にあるマージされていないすべてのリーフの公開鍵を使って暗号化する必要があります。リーフは最初に追加された時点ではマージされていません。これは、リーフを追加するプロセスでは、ツリー内のそのリーフより上にあるすべてのノードにアクセスできないためです。リーフは、セクション5.4で説明したように、ノードの秘密鍵を受け取ると「マージ」されます。

5.4. ラチェットツリーの進化

MLS グループのメンバーは、グループの共有秘密に追加される新しい鍵をグループに提供するこ とで、前方秘匿と妥協後のセキュリティを提供するための鍵スケジュールを進める。そのためには、グループの一人のメンバが新しい鍵を生成し、それを自分のローカルな木の状態に 適用し、その鍵を UpdatePath メッセージ(セクション 7.7 参照)によってグループの他のメンバに送 る。他のグループメンバーは、UpdatePath に含まれるキーマテリアルを自分のローカルツリーの状態に適用し、グループの最新の共有秘密を導き出します。

まず、UpdatePathの生成者は、そのリーフのKeyPackageと、ルートへの直接のパスを、新しい秘密の値で更新する。KeyPackage内のHPKEリーフ公開鍵は、妥協後のセキュリティを提供するために、新たに生成されたHPKE秘密鍵に由来しなければならない(MUST)。

UpdatePathの生成者は、"leaf_secret "と呼ばれる新鮮なランダム値をサンプリングすることから始め、leaf_secretを使用して、リーフのHPKEキー・ペアを生成し(セクション7参照)、リーフの祖先ごとに1つずつ、一連の "パス・シークレット "をシードする。この設定では,path_secret[0]はリーフの直上のノードを指し,path_secret[1]はその親を指し,以下同様である.各ステップでは,パスシークレットを用いて,対応するノードの新しいシークレット値を導出し,そこからノードのキーペアを導出する.

作成者以外のコメントは許可されていません