Intel SGX - Remote Attestation概説

# 概要
最近(と言っても数年前ですが)Intelよりリリースされた秘密計算機構であるIntel SGXに、Remote Attestation(以下RA)と呼ばれる遠隔認証プロトコルがあります。これは、Intel社に提供されている開発者向けリファレンス[3]等を読むとよく「リモートからSGXマシン上のEnclaveを検証してデータを送るためのプロトコル」という書かれ方をしています。

しかし、これだけでは実際に何をどうしているのかイマイチ分からず、開発者向けリファレンスを読んでも中途半端に抽象的であり、正直曖昧さが否めません。SGXSDKにはサンプルコードが含まれていますが、SGXSDK中のRAのサンプルコードはかなり短絡処理が行われており、正直当てになる代物ではありません。

よって、本格的に実装したい場合には、曖昧な開発者向けリファレンスを読みながら対応しそうなSGXSDK中のAPIの仕様を洗いざらい読んで自力で組み立てる必要がありました。

この状態が長らく続いていたのですが、2018年の8月により詳細なサンプル[2]とそれを参照した仕様の記事[1]がセットでIntelにより公開され、大分取っ掛かりやすくなりました。


本記事では、それらの情報を含め、「RAが何たるものでどう動いているのか」を簡単に解説しようと思います。

なお、あくまでRAの解説ですので、Intel SGXの基本的な知識についてはここでは省きます。

**2020/1/6追記:** 後半のRAのプロトコル詳細説明において、暗号鍵に割り当てた記号の曖昧さを修正しました。


# 用語集
本記事で使用する、あまり一般的でなさそうな用語リストです。

| 用語  | 説明 |
|---------|----------------------|
|  SP  | Service Provider. 非SGX側の端末。|     |
| ISV  | Independent Software Vendor. SGX搭載でEnclaveを有するマシン。|
| IAS  | Intel Attestation Service. RAの仲介をしてくれる第三者検証機関です。|
| AE   | Architecture Enclave. SGXがCPUからの秘密鍵取得などの内部処理のために使うEnclave達です。|
| PSE  | Platform Service Enclave. SGXによるtrustedなモノトニックカウンタ等の利用を可能にするEnclaveです。|
| APP  | Untrusted領域、つまりSGXマシンのEnclave外で動作するプログラムを本記事ではこう呼びます。対義語は「Enclaveコード」とします。|

ISVとSPについて、どちらがサーバでどちらがクライアントなのかはSGXの使い方によります。IntelのサンプルコードのようにDRM処理にSGXを使いたいならばISVがクライアントですし、リモートサーバにSGXを搭載して秘密計算をさせたいならばISVがサーバとなります。

# 簡単に言うとRAとは何なのか
説明がどうしても長くなるので、最初にRAの根本のコンセプトをごく簡単に述べると、

- SP(非SGX側)が、ISV(SGX側)のCPUとEnclaveがSGXとしての完全性を保っているかを検証する
- SPとISV間での安全なデータ送受信のため、共通鍵の鍵交換を行う(要はTLSセッションを確立する)

と、とても簡潔に言い表すことが出来ます。形としては、鍵交換プロトコルにISVの完全性チェックを挟み込んでいる感じです。

では、以下ではその詳細についてもう少し掘り下げていきます。

# RA解説
RAは、Intelの文書にて「シグマプロトコルの形を取ったもの(Sigma-like protocol)」と言われている通り、ベースとして楕円曲線ディフィーヘルマン鍵共有プロトコルを用い、そこの各所にISVを検証する様々な仕組みを差し込んだようなプロトコルです。

### 楕円曲線ディフィーヘルマン鍵共有プロトコル
楕円曲線ディフィーヘルマン鍵共有プロトコル(ECDHKE)とは、名前の通り楕円曲線暗号を用いたディフィーヘルマン鍵共有プロトコル(DHKE)で、信頼できない通信路上で公開鍵暗号(楕円曲線暗号)を用いて共通鍵を共有するためのプロトコルです。

楕円曲線暗号がどのような仕組みの暗号であるかの詳細はここでは省きますが、要は公開鍵暗号の一種です。
事前に共有しておくべき前提情報として

- 楕円曲線**G**
- 楕円曲線上の開始座標(ベースポイント)**Q**

があります。Intel SGXにおいては、NIST P-256と呼ばれるコンテキストを用いるように定めています。

次に、公開鍵として**P**、秘密鍵として**n**を用います。公開鍵**P**は、ベースポイント**Q**に対して秘密鍵**n**だけ「楕円曲線上の乗算」を行った後の座標です(要は楕円曲線上の乗算でP=nQ)。楕円曲線上の乗算(あるいは加算)については、ここでは省略します。

ここで、ECDHKEを行うユーザとしてAliceとBobがいるとします。2人は、前提情報の**G**, **Q**に基づいてそれぞれの公開鍵と秘密鍵を生成します。
つまり、Aliceは(Pa, na)のキーペアを生成し、Bobは(Pb, nb)のキーペアを生成することになります。

ここで、楕円曲線暗号には「**自分の公開鍵と相手の秘密鍵の積**と**自分の秘密鍵と相手の公開鍵の積**が**同値になる**」、つまり

``` math
P_a n_b = P_b n_a
```

という特性があります(これは楕円曲線上の加算の特性です)。よって、AliceとBobは自分の公開鍵だけを差し出せば、受け取った公開鍵を自分の秘密鍵とそれぞれ乗算することで、2者ともに同じ値(つまり共通鍵)を取得することが出来るのです。

以上の流れを図に表すと以下のようになります(別の機会に用いた図の流用なのでAliceとBobがSPとISVに置き換わっていたり英語だったりしますが)。
<img src="https://qiita-image-store.s3.amazonaws.com/0/349383/56b5d358-10de-f137-bc7b-0ad2e950a9ff.png">

### RAのフロー概要
さて、何度も言いますように、RAの基本は上述したECDHKEです。しかし、ECDHKEで出来ることに加えて、SPはISVのCPUとEnclaveの完全性を検証しなければなりません。その仕組みをECDHKEに差し込んで完成したものが、以下の図に非常に簡略化したフローを示したRAです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/349383/65875ceb-cdfd-30a1-ac7d-007578548775.png">

大きな違いとして、ECDHKEのケースに加えて、新たにIASが第三者検証機関として登場しました。また、CPU情報の検証、ISV上のEnclaveの検証が発生しているのが何となく分かると思います。

厳密なプログラム的に何をしているかはすぐ後に詳しく述べますが、大まかなフローを順序通りに列挙すると  
1. ISVがSPに楕円曲線暗号の公開鍵とCPU情報を送る
2. SPは受け取った公開鍵を自分の秘密鍵と組み合わせて共通鍵を取得する
3. 更に、SPはISVのCPU情報をIASに送り検証してもらう
4. SPはIASからの検証結果を、自分の公開鍵と同時に(改竄できない形で)ISVに送信する。
5. ISVは検証結果をSGXのTrustedライブラリで検証し、RAが続行できるかをチェックする。
6. 大丈夫なら、ISVも共通鍵を取得する
7. まだEnclaveの検証が終わっていないので、Enclave情報をSP経由でIASに検証してもらう
8. SPは検証結果に基づき、RAを受諾する(要するに相手のCPUとEnclaveを信用する)か失効させるかを決定する

というものになります。
では、厳密にどのような処理を行っているのかを説明していきます。

### RAのフロー詳説
Intelによる開発者向けリファレンスと最近リリースされたサンプルコード向けの解説をわかりやすいように組み合わせて説明に使っているため、厳密な関数名等は各サンプルコードの実装と異なる可能性が十二分にあります。

#### RA開始手続・msg0生成
`msg0`はISV→SPの方向に送信されるコンテキストで、後述の`msg1`と合体させて送信しても構いません。

0. SPが何らかの形でISVにRA開始要求(Challange)を送信します。これはどんな形でもよく、単にISVが待ち受けているポートにSPが接続することとしても全然構いません。

1. ISVのAPPがEnclaveを作成し、`enclave_init_ra()`を起動します。引数はPSEを使用するか否かを指定する`b_pse`です。PSEについてですが、単にISVとSP間でファイル送受信をしたい程度ならば、まず使う必要は無いでしょう。

2. (2.の`b_pse=true`だった場合)ISVのEnclaveコードは`sgx_create_pse_session()`によりAE操作ライブラリを呼び、PSEとのセッションを設立します。

3. ISVのEnclaveコードが`sgx_ra_init()`を呼び出します。引数はSPのEC(楕円曲線)DSA公開鍵`g_sp_pub_key`と、前述のPSE使用有無フラグの`b_pse`です。この公開鍵は改竄されてはならないので、ISVのEnclaveコードにハードコーディングしておく事が強く推奨されています。

4. (2.の`b_pse == true`だった場合)ISVのEnclaveコードは`sgx_close_pse_session()`によりPSEとのセッションを閉じます。ISVのEnclaveがプラットフォームサービスを使う場合には、Enclaveコードが`sgx_ra_init()`を呼び出す前にPSEとのセッションを確立していなければなりません。

5. 4.でISVのEnclaveコードが呼び出した`sgx_ra_init()`は、鍵交換に関する情報をリターンし、EnclaveコードはそれをISVのAPPにリターンします。

6. ISVのAPPは`sgx_get_extended_epid_group_id()`を呼び出し、戻り値`p_extended_epid_group_id`を取得します。その値を`msg0`としてSPに送信します(`msg0`のフォーマットは設計者依存です)。
この「EPID」こそがISVのCPUに関するコンテキストの役割を果たすもので、Intelが開発したDAA（direct anonymous attestation）拡張なのですが、詳細はここでは省きます。

7. SPは、ここで簡易的にEPIDグループIDが対応しているかを検証します。非対応ならばRAを強制終了します。具体的には、現時点でIDが0でなければ棄却する必要があります。

#### msg1生成
`msg1`は`msg0`と合体させて送っても大丈夫なコンテキストで、ISV→SP方向です。

8. ISVのAPPは`sgx_ra_get_msg1()`を呼び出します。この時、引数として鍵交換情報と、`sgx_ra_get_ga()`への(Untrustedなプロキシ)関数ポインタを渡します。`sgx_ra_get_ga()`は、ISV側のDHKE用秘密情報を計算するためのものです。

9. `sgx_ra_get_msg1()`は`msg1`を生成します。`msg1`はDHKEにて用いる暗号のECDSA-256bitキーペア`Ga`の内の公開鍵`Ga_pub`と、ISVのEPID-GIDである`GID`を含む構造体です。

#### msg2生成
`msg2`は`msg0`, `msg1`に応えてSPがISVに送信するコンテキストです。

* ISVのAPPは`ra_network_send_receive()`関数により`msg1`をSPに送信します。SPは受信したら、`sp_ra_proc_msg1_req()`関数を起動し、`msg1`から`msg2`を生成します。具体的には、ISVのDHKE情報を検証後、SP自身のDHKE情報を生成し、インテルの検証機関IASに署名失効リストを要求します。更に詳細な手続きを記述すると、以下の通りになります：  
    1. NIST P-256曲線を用いて乱数的な楕円曲線暗号のキーペアを生成します。このキーペアを`Gb`と定義します。    
    2. `Ga_pub`(ISV用のECDSA公開鍵)及びキーペア`Gb`の内の秘密鍵`Gb_priv`を用いて、鍵導出鍵(KDK)を以下の手続きにて生成します：
        1. `Ga_pub`と`Gb_priv`を用いて共有秘密鍵を生成します。この共有秘密鍵は、`Gab`のx軸成分に相当する`Gab_x`と定義されます。

        2. `Gab_x`のバイト列を反転させる事で、バイトオーダをリトルエンディアンに変換します。
        
        3. リトルエンディアン化した`Gab_x`について、AES-128bitのCMACを実行します。この時、`0x00`番目のブロックを鍵として使用します。
        
        4. 生成したCMAC(ブロック暗号的メッセージ認証コード)をKDKとします。
    3. 以下のバイト列
        
        ```
        0x01 || SMK || 0x00 || 0x80 || 0x00
        ```
    に対して、KDKを鍵として再度AES-128bit CMACを適用し、SMK(恐らくService Master Keyの略かと思われます)を生成します。なお、`||`は連結を表す為の便宜上の表現であり、バイト列中の「SMK」は文字列リテラルであり、二重引用符で囲う必要はありません。C/C++ソースコード内での表記方法により近い表記をするならば、これは`\x01SMK\x00\x80\x00`のようになります。

- 以上によりKDK及びSMKを生成したら、以下の手続きを踏み、`msg2`用の情報を充足させて行きます。  
  1. quoteタイプを指定します。`0x0`ならunlinkable、`0x1`ならlinkableとなります。簡単に言えば、 linkableならばRA上で使用するEnclaveメタデータ`quote`が同一プラットフォーム上で生成されたかを追跡することが出来るようになります(旧称:Name Based Mode)。ちなみに、これはIntelにRA用の自己署名証明書を登録する際に選択することが出来ます。      
  2. `KDF_ID`(鍵導出関数識別番号)を指定します。通常は`0x1`にします。
  3. 以下のバイト列
        
        ```
        Gbx_pub || Gby_pub || Gax_pub || Gay_pub
        ``` 
        についてのECDSA署名`r||s`を、SPの楕円曲線暗号秘密鍵を用いて生成します。これを`SigSP`とします。なお、楕円曲線暗号は、x軸成分とy軸成分により構成されています(例: `Ga_pub`は`Gax_pub`と`Gay_pub`による)。  
  4. 以下のバイト列
        
        ```
        Gb_pub || SPID || Quote_Type || KDF_ID || SigSP 
        ```

        に対してSMKを鍵としてAES-128bit CMACを生成します。これを`CMAC_SMK(A)`とします。  
  5. ISV側のEPID-GIDについて、IASに署名失効リストとの照合を要求します。第三者検証機関としてIASを利用している限りでは必ず0になるので蛇足に思えるかも知れませんが、例えばCPU内のEPIDが壊れていれば0にならない可能性も大いにありますし、別の検証機関を導入した場合には話が変わってくるので、必要な処理です。

* `msg2`の中身は以下の通りです：
 * `Gb_pub`: SP側の乱数的なECDSA鍵
 * `SPID`: サービスプロバイダID(Intelに登録してメールで貰うもの)
 * Quoteタイプ: Enclaveの同一生成プラットフォーム追跡の有無
 * `KDF-ID`: 鍵導出関数ID。通常は1。
 * `SigSP`: SP側のECDSA署名
 * `CMAC_SMK(A)`: 上記5つのデータ(Aと呼ぶ)に対するSMKを鍵としたCMAC
 * `SigRL`: IASから取得した署名失効情報  

    この`msg2`をSPからISVに`msg1`への応答として送信します。

#### msg3生成
`msg3`は、`msg2`を受けてISVがSPに送信するコンテキストです。

12. ISVのAPPは`sgx_ra_proc_msg2()`関数を起動します。引数は`sgx_ra_proc_msg2()`へのポインタと、`sgx_ra_get_msg3_trusted()`へのポインタです。これらのポインタはそれぞれ`msg2`、`msg3`を扱うものであり、edger8rにより自動生成されます。そのためには、同様のケースと同じくして、`sgx_tkey_exchange.edl`及び`sgx_tkey_exchange.h`をEnclaveのEDLにインポートする必要があります。
`sgx_ra_proc_msg2()`は、ISVがSPの署名が正当であるかを検証し、署名失効情報を確認後、`msg3`を生成するものです。

13. `sgx_ra_proc_msg2()`が`msg3`を生成します。`msg3`は、ISV側のEnclaveのメタデータである`Quote`構造体とPSEの正当性をSP及びIASが検証するための構造体です。`msg3`は以下のように構成されています:
  * `CMAC_SMK(M)`: 以下の3つのデータを`M`とし、それに対しSMKを鍵として生成したCMAC(SMKは`msg2`中の`Gb`を用いることでISV側で再計算されるものであると思われます)。
  * `Ga_pub`: ISVのECDSA公開鍵
  * `PS_SECURITY_PROPERTY`: PSEの正当性情報
  * `QUOTE`: ISVのEnclaveのメタデータを表す構造体

#### msg4生成
`msg4`は、`msg3`を受けてSPがISVに送信するRA受諾可否に関するコンテキストです。

14. ISVのAPPは`msg3`をSPに送信します。SPは、受信した`msg3`について、以下の手順で検証を行います。
    1. `msg3`中のISVのECDSA鍵である`Ga_pub`が`msg1`中のものと同一であるかを確かめます。
    2. `CMAC_SMK(M)`を検証します。
    3. Reportデータの先頭32バイトが、以下のバイト列
    
        ```
        Ga_pub || Gb_pub || VK
        ```
        のSHA-256ハッシュ値と一致するかを検証します。VK(Verification Key)は、KDKを鍵として以下のバイト列
     
        ```
        0x01 || "VK" || 0x00 || 0x80 || 0x00
        ```        
    　  に対して生成したAES-128bitのCMACです。<br>
    4. ISVから受信されたアテステーション証明情報を以下の手続きで検証します：  
      　1. `Quote`構造体を`msg3`から抽出します。
      　2. アテステーション証明情報を検証するためのAPI関数を呼び出し、`Quote`をIASに提出します。IASからは、応答としてアテステーションレポートが返されます。アテステーションレポート、レスポンスヘッダに加え、Enclave及びPSEに対するアテステーションステータス(RAを完了させてもOKか否か)を含みます。
      　3. アテステーションレポート応答にて受信した署名証明書を検証します。
      　4. アテステーションレポートの署名を署名証明書を用いて検証します。
      　
    5. 上記までによりQuote構造体の正当性を確認できたら、以下の手続きを実行します:
      　1. ISVのEnclave(及びもしあればPSE)についてのアテステーションステータスを抽出します。
      　2. Enclaveの完全性、セキュリティバージョン、プロダクトIDを検証します。
      　3. ISVのEnclave(及びもしあればPSE)を信頼するか否かを決定します。
      　
    6. ISVとSPはそれぞれ、セッション鍵のSK及びMKの両方を導出します。SK、MKは以降ISVとSP間でデータを送受信する際に用いられます。ISV側は単に`sgx_ra_get_keys()`を実行するだけで得られますが、SP側は自前でこれらの鍵を生成する必要があります。具体的には、それぞれ以下のバイト列:  
        
        ```
        MK: 0x01 || "MK" || 0x00 || 0x80 || 0x00  
        SK: 0x01 || "SK" || 0x00 || 0x80 || 0x00
        ```  
        に対してKDKを鍵として生成されたAES-128bitのCMACを生成します。

    7​. `msg4`を生成しISVに送信します。`msg4`のフォーマットは設計者依存ですが、最低限Enclaveの信用可否、及びもし存在すればPSEマニフェストの信用可否を含まねばなりません。

15. アテステーションステータスがOKでなかった場合、IASは追加でPIB(プラットフォーム情報ブロブ)を`msg4`に含めてSPに送信する場合があります。SPは任意でPIBをISVに転送できます。ISVはPIBを渡されたら、`sgx_report_attestation_status()`関数にPIBを渡して起動することで、失敗に関するより詳細な情報を取得することが出来ます。


以上がRAで行われるフローですが、この辺りは新しくIntelが出したRAのサンプルコードを流用すれば、RA部分は自前で実装する必要はありません。RA後に行いたい処理をRA処理の後に書き込んでやるだけです。

# その他
- 新しいRAサンプルにおいては、ISVとSP間の通信にMsgIOと呼ばれるクラスのMsgIO->read()及びMsgIO->send()を用いて送受信を行っています。この関数は(~~Intelらしく~~)非常に不安定なため、AES暗号文のように無秩序な文字を含むデータを送受信する際には一度Base64に変換することを強くおすすめします。 

- RA後のISVとSP間での通信では、送りたいファイルに先ほど導出したSKまたはMKをキーとして128bit AES/GCM暗号化を施して行います。AES/GCMでは12バイトの初期化ベクトルの他にタグと呼ばれる16バイトのMACも必要ですので、データ送信時には暗号文、初期化ベクトル(乱数的に生成した公開情報)、タグ(AES/GCM暗号化時に得られる公開情報)の3つを送る必要があります。

- SP側での暗号処理はOpenSSLライブラリで行い、ISV側での暗号処理は`sgx_rijndael128GCM_encrypt()`及び`sgx_rijndael128GCM_decrypt()`で行います。AES/GCM暗号の暗号文の文字列長は平文の長さと同値です。

# まとめ
結局の所、RAを用いた実装を行う上で必ず覚えておくべきことは、SKまたはMKを共通鍵として使用できること、また[2]のコードを流用するならばMsgIOによって通信が行われていることでしょう。

なお、[2]のコードは前述の通りDRM処理のモデルで実装されているため、ISVをサーバとして利用したい場合にはMsgIOのサーバ/クライアント設定を逆にする必要があります。


# 参考文献
[1][Code Sample: Intel® Software Guard Extensions Remote Attestation End-to-End Example](https://software.intel.com/en-us/articles/code-sample-intel-software-guard-extensions-remote-attestation-end-to-end-example)
[2][GitHub - Intel® Software Guard Extensions (SGX) Remote Attestation End-to-End Sample](https://github.com/intel/sgx-ra-sample)
[3][Intel® Software Guard Extensions SDK for Linux* OS Developer Reference ](https://download.01.org/intel-sgx/linux-2.0/docs/Intel_SGX_SDK_Developer_Reference_Linux_2.0_Open_Source.pdf)