概要
最近(と言っても数年前ですが)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)のキーペアを生成することになります。
ここで、楕円曲線暗号には「自分の公開鍵と相手の秘密鍵の積と自分の秘密鍵と相手の公開鍵の積が同値になる」、つまり
P_a n_b = P_b n_a
という特性があります(これは楕円曲線上の加算の特性です)。よって、AliceとBobは自分の公開鍵だけを差し出せば、受け取った公開鍵を自分の秘密鍵とそれぞれ乗算することで、2者ともに同じ値(つまり共通鍵)を取得することが出来るのです。
以上の流れを図に表すと以下のようになります(別の機会に用いた図の流用なのでAliceとBobがSPとISVに置き換わっていたり英語だったりしますが)。
RAのフロー概要
さて、何度も言いますように、RAの基本は上述したECDHKEです。しかし、ECDHKEで出来ることに加えて、SPはISVのCPUとEnclaveの完全性を検証しなければなりません。その仕組みをECDHKEに差し込んで完成したものが、以下の図に非常に簡略化したフローを示したRAです。
大きな違いとして、ECDHKEのケースに加えて、新たにIASが第三者検証機関として登場しました。また、CPU情報の検証、ISV上のEnclaveの検証が発生しているのが何となく分かると思います。
厳密なプログラム的に何をしているかはすぐ後に詳しく述べますが、大まかなフローを順序通りに列挙すると
- ISVがSPに楕円曲線暗号の公開鍵とCPU情報を送る
- SPは受け取った公開鍵を自分の秘密鍵と組み合わせて共通鍵を取得する
- 更に、SPはISVのCPU情報をIASに送り検証してもらう
- SPはIASからの検証結果を、自分の公開鍵と同時に(改竄できない形で)ISVに送信する。
- ISVは検証結果をSGXのTrustedライブラリで検証し、RAが続行できるかをチェックする。
- 大丈夫なら、ISVも共通鍵を取得する
- まだEnclaveの検証が終わっていないので、Enclave情報をSP経由でIASに検証してもらう
- SPは検証結果に基づき、RAを受諾する(要するに相手のCPUとEnclaveを信用する)か失効させるかを決定する
というものになります。
では、厳密にどのような処理を行っているのかを説明していきます。
RAのフロー詳説
Intelによる開発者向けリファレンスと最近リリースされたサンプルコード向けの解説をわかりやすいように組み合わせて説明に使っているため、厳密な関数名等は各サンプルコードの実装と異なる可能性が十二分にあります。
RA開始手続・msg0生成
msg0
はISV→SPの方向に送信されるコンテキストで、後述のmsg1
と合体させて送信しても構いません。
-
SPが何らかの形でISVにRA開始要求(Challange)を送信します。これはどんな形でもよく、単にISVが待ち受けているポートにSPが接続することとしても全然構いません。
-
ISVのAPPがEnclaveを作成し、
enclave_init_ra()
を起動します。引数はPSEを使用するか否かを指定するb_pse
です。PSEについてですが、単にISVとSP間でファイル送受信をしたい程度ならば、まず使う必要は無いでしょう。 -
(2.の
b_pse=true
だった場合)ISVのEnclaveコードはsgx_create_pse_session()
によりAE操作ライブラリを呼び、PSEとのセッションを設立します。 -
ISVのEnclaveコードが
sgx_ra_init()
を呼び出します。引数はSPのEC(楕円曲線)DSA公開鍵g_sp_pub_key
と、前述のPSE使用有無フラグのb_pse
です。この公開鍵は改竄されてはならないので、ISVのEnclaveコードにハードコーディングしておく事が強く推奨されています。 -
(2.の
b_pse == true
だった場合)ISVのEnclaveコードはsgx_close_pse_session()
によりPSEとのセッションを閉じます。ISVのEnclaveがプラットフォームサービスを使う場合には、Enclaveコードがsgx_ra_init()
を呼び出す前にPSEとのセッションを確立していなければなりません。 -
4.でISVのEnclaveコードが呼び出した
sgx_ra_init()
は、鍵交換に関する情報をリターンし、EnclaveコードはそれをISVのAPPにリターンします。 -
ISVのAPPは
sgx_get_extended_epid_group_id()
を呼び出し、戻り値p_extended_epid_group_id
を取得します。その値をmsg0
としてSPに送信します(msg0
のフォーマットは設計者依存です)。
この「EPID」こそがISVのCPUに関するコンテキストの役割を果たすもので、Intelが開発したDAA(direct anonymous attestation)拡張なのですが、詳細はここでは省きます。 -
SPは、ここで簡易的にEPIDグループIDが対応しているかを検証します。非対応ならばRAを強制終了します。具体的には、現時点でIDが0でなければ棄却する必要があります。
msg1生成
msg1
はmsg0
と合体させて送っても大丈夫なコンテキストで、ISV→SP方向です。
-
ISVのAPPは
sgx_ra_get_msg1()
を呼び出します。この時、引数として鍵交換情報と、sgx_ra_get_ga()
への(Untrustedなプロキシ)関数ポインタを渡します。sgx_ra_get_ga()
は、ISV側のDHKE用秘密情報を計算するためのものです。 -
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に署名失効リストを要求します。更に詳細な手続きを記述すると、以下の通りになります:-
NIST P-256曲線を用いて乱数的な楕円曲線暗号のキーペアを生成します。このキーペアを
Gb
と定義します。 -
Ga_pub
(ISV用のECDSA公開鍵)及びキーペアGb
の内の秘密鍵Gb_priv
を用いて、鍵導出鍵(KDK)を以下の手続きにて生成します:-
Ga_pub
とGb_priv
を用いて共有秘密鍵を生成します。この共有秘密鍵は、Gab
のx軸成分に相当するGab_x
と定義されます。 -
Gab_x
のバイト列を反転させる事で、バイトオーダをリトルエンディアンに変換します。 -
リトルエンディアン化した
Gab_x
について、AES-128bitのCMACを実行します。この時、0x00
番目のブロックを鍵として使用します。 -
生成したCMAC(ブロック暗号的メッセージ認証コード)をKDKとします。
-
-
以下のバイト列
0x01 || SMK || 0x00 || 0x80 || 0x00
||
は連結を表す為の便宜上の表現であり、バイト列中の「SMK」は文字列リテラルであり、二重引用符で囲う必要はありません。C/C++ソースコード内での表記方法により近い表記をするならば、これは\x01SMK\x00\x80\x00
のようになります。 -
- 以上によりKDK及びSMKを生成したら、以下の手続きを踏み、
msg2
用の情報を充足させて行きます。-
quoteタイプを指定します。
0x0
ならunlinkable、0x1
ならlinkableとなります。簡単に言えば、 linkableならばRA上で使用するEnclaveメタデータquote
が同一プラットフォーム上で生成されたかを追跡することが出来るようになります(旧称:Name Based Mode)。ちなみに、これはIntelにRA用の自己署名証明書を登録する際に選択することが出来ます。 -
KDF_ID
(鍵導出関数識別番号)を指定します。通常は0x1
にします。 -
以下のバイト列
Gbx_pub || Gby_pub || Gax_pub || Gay_pub
についてのECDSA署名
r||s
を、SPの楕円曲線暗号秘密鍵を用いて生成します。これをSigSP
とします。なお、楕円曲線暗号は、x軸成分とy軸成分により構成されています(例:Ga_pub
はGax_pub
とGay_pub
による)。 -
以下のバイト列
Gb_pub || SPID || Quote_Type || KDF_ID || SigSP
に対してSMKを鍵としてAES-128bit CMACを生成します。これを
CMAC_SMK(A)
とします。 -
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に送信するコンテキストです。
-
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
を生成するものです。 -
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受諾可否に関するコンテキストです。
-
ISVのAPPは
msg3
をSPに送信します。SPは、受信したmsg3
について、以下の手順で検証を行います。-
msg3
中のISVのECDSA鍵であるGa_pub
がmsg1
中のものと同一であるかを確かめます。 -
CMAC_SMK(M)
を検証します。 -
Reportデータの先頭32バイトが、以下のバイト列
Ga_pub || Gb_pub || VK
のSHA-256ハッシュ値と一致するかを検証します。VK(Verification Key)は、KDKを鍵として以下のバイト列
0x01 || "VK" || 0x00 || 0x80 || 0x00
に対して生成したAES-128bitのCMACです。
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マニフェストの信用可否を含まねばなりません。 -
-
アテステーションステータスが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
[2]GitHub - Intel® Software Guard Extensions (SGX) Remote Attestation End-to-End Sample
[3]Intel® Software Guard Extensions SDK for Linux* OS Developer Reference