実行の完全性を実現するSGX Remote Attestation | Anonify解体新書2
前回の内容はこちら
TL;DR
セキュリティ・プライバシー保護技術Anonifyの主要技術要素について解説する連載記事(全8回)
Anonifyで使用されている技術要素を洗い出し重要な技術について簡単なサンプルプログラムを交えて解説する。
その2では「実行の完全性を実現するSGX Remote Attestation」と題してRemote Attestationのプログラムについて解説する。
実行の完全性 = Execution Integrity
サンプルプログラムはRustで開発
ここで使用するコードはすべて独立して動作するのでAnonify自体の知識やAnonifyの動作環境は不要
今回のサンプルは残念ながらMac上でコンパイルはできるものの実行はできないので、実行する場合はIntel SGXのVMを用意すること
この記事の中で使用する図は特別な記載がない限り全て筆者が作成したもの
サンプルコード
Anonifyが使用している主な技術要素
TEE(Intel SGX)関連
OCall/ECall
Remote Attestation <- 今回解説するのはここ
crypto_box(NaCl)
データのシーリング
mutual-TLS
Blockchain関連
スマートコントラクト
Web3
プログラムの完全性をどう保証するか
そのマシンのEnclaveは本当に安全なのか、ちゃんとEnclave上でプログラムが実行されているか
この辺りを証明できないとSGXを使うメリットがない
このプログラムはEnclaveで安全に実行されてますと開発者が主張してもだれも信じてくれない(信じちゃいけない)
以下の二つを証明する必要がある
Enclaveで実行されるプログラムが正当なSGXマシン上でビルドされているか
ビルドの証明
Enclaveで実行されるプログラムが正当なSGXマシン上で実行されているか
実行の証明
ビルドの証明にはMRENCLAVEやMESIGNERを使う
今回は詳しく説明しない、データのシーリングで詳しく説明する予定
実行の証明にはRemote Attestationを使う
今回の記事のメインテーマ
Remote Attestation(RA)とは
SGXマシンのEnclaveをリモートから検証するためのプロトコル
リモート = 第3者機関 = Intel
正規のCPUとEnclave上でプログラムが実行されているかをリモートにあるIntelのサービスに検証してもらう
プログラム開発側がいくらEnclaveで開発してますと主張しても信用されない(そんなの信用しちゃいけない)ので第3者機関に検証してもらう
AnonifyでもちゃんとRemote Attestationやってます
参考ページ
この記事が一番わかりやすかった
事前準備
SGXの開発環境の構築方法はこちらを参照のこと
Remote AttestationするにはIntelのWeb APIへのアクセスが必要なため事前に開発者登録が必要
RAの実装は2パターンある
Intelのドキュメントを参考にmsg0,msg1,msg2,msg3,msg4を生成しながら検証するパターン
glassonion1.icon api.trustedservices.intel.comにアクセスするタイミングがわからん...
非Enclave側に主要ロジックを書くことになる
msg0-4を使わないパターン
こちらの方が難易度低いので今回のサンプルはこちらを採用している
Enclave側で主要ロジックを書くことができる
Rust SGX SDKのサンプルコードはこちらのパターン
Anonifyもこちらのパターンを採用している
Remote Attestationの流れ(ざっくり)
msg0-4を使わないパターンの流れ
1. EPID(Enhanced Privacy ID)グループIDとTargetInfoの取得
Intel Provisioning ServiceからEPIDグループIDを取得する
TargetInfoにはEnclaveの情報が格納されている
2. Intel Attestation ServiceからSigRL(Signature Revocation List)を取得する
SigRL = 除外リスト
EPIDグループIDを渡してSigRLをもらう
3. TargetInfoからReportを生成する
実際のデータはReport Bodyの中に格納されている
Report Bodyの中のReport Dataと呼ばれる領域にユーザ定義データを64バイトまで自由に埋め込むことができる
4. Quoteを取得する
引数はSPID、SigRL、Nonce、Report
Intel Provisioning ServiceからQuoteを取得する
5. Reportを検証する
6. Intel Attestation ServiceにQuoteを送信して検証結果(アテステーションレポート)を取得する
7. アテステーションレポートを検証する
ISV Enclave Quote Status(アテステーションステータス)の検証
Quoteの検証をする
QuoteのReport Bodyの中にMRENCLAVEとMRSIGNERが存在する
MRENCLAVEとMRSIGNERの詳細は以下のページを参照のこと
Intelのリモート検証サービスは2つある
Intel Attestation Service(IAS)
SigRLの取得とQuoteの検証時にアクセスする
REST API
Intel Provisioning Service(IPS)
EPIDグループIDの取得とQuoteの取得時にアクセスする
Provisioning Enclave (PvE)またはQuoting Enclave (QE)と呼ばれる特別なEnclave領域からアクセスする
PvE/QE以外にもLaunch Enclave (LE)やPlatform Service Enclave (PSE)など特別なEnclaveが存在する。これらのEnclaveをまとめてArchitectural Enclavesと呼ぶ
SGX SDKのsgx_init_quote関数やsgx_get_quote関数なんかを呼ぶとその中で通信がはしる
番外編: Enclaveの種類、Architectural Enclaves
Remote Attestationの資料を読んでいるといろんな種類のEnclaveがでてきて、わけわからんとなることが多い。開発者が直接使用するEnclave以外のSGX側で用意している特別なEnclave達をArchitectural Enclaves(AE)と呼ぶ。AEはへのアクセスは /opt/intel/sgx-aesm-service/aesm/aesm_service デーモンからしかできない。開発者が使うEnclave(Application Enclave略してApp Enclave)から直接アクセスはできない。
AEには以下のEnclaveが存在する
Launch Enclave (LE)
Enclave起動時にEnclaveの有効性を確認するためのEnclave
Provisioning Enclave (PvE)
IPSと通信をしてEPIDをグループに追加するためのEnclave
EPIDはCPUごとに固有のID、IPSと通信をしてEPIDをグループに追加してもらう
EPIDが追加されたグループのIDのことをEPIDグループIDと呼ぶ
同一グループ内で使用できる秘密鍵のことをEPID秘密鍵と呼ぶ
一つの公開鍵に対して複数の秘密鍵を紐付ることができる
レポートの署名に使用する
glassonion1.icon 暗号化に使うわけではなさそう、あくまで署名用の鍵という理解、暗号化に使うとマシン変わると復号できないしそこまではやらないっぽい
msg0-4の生成まわりもこのEnclaveから行う。今回のサンプルではmsg系を使用してないのでEPIDグループID取得のみ
Provisioning Certificate Enclave(PcE)
プロセッサ証明書に署名をするためのEnclave
Quoting Enclave (QE)
PvEが保持しているEPID秘密鍵に対して唯一アクセス権を持っているEnclave
ReportにPvEから提供されたEPID秘密鍵を使って署名をし、Quoteを生成する
Platform Service Enclaves (PSE)
モノトニックカウンターや信頼できる時間などを他のEnclaveに提供するためのEnclave
図にすると以下のようになる(PlantUML)。Application Enclaveは開発者が使うEnclave(以後App Enclaveと表記する) https://gyazo.com/162104461f6b415665f73015af933dc2
Remote Attestation文脈だとPvEとQEを使用する。AEの詳細は以下のページを参照のこと
Remote Attastationのプログラムをかいてみる
Remote Attestationを実行するプログラム
Enclaveの中でRemote AttestationをしてEnclaveの検証をする
今回は割と複雑なプログラムなのでいくつがライブラリを使用している
IntelのAPIにアクセスするために http_req というライブラリを使用する
Enclaveの中では tokio が使えないので reqwest のようなメジャーライブラリが使用できない
Jsonデータのデシリアライズに serde_json を使用する
Intelの署名検証に webpki を使う
今回紹介するサンプルコードの完全版はこちら
サンプルプログラムの構成
code: remoteattestation
remoteattestation/ サンプルプログラムのディレクトリ
├ Makefile
├ app/
│ ├ Cargo.toml
│ ├ build.rs
│ └ src/
│ └ main.rs
├ enclave/
│ ├ Cargo.toml
│ ├ ca.crt # IntelのRootCAファイル
│ ├ Enclave.config.xml
│ ├ Enclave.edl # EDL定義ファイル
│ ├ Enclave.lds
│ ├ Enclave_private.pem
│ └ src/
│ ├ client.rs # IASに接続するためのコード
│ └ lib.rs
└ lib/ # 空でOKコンパイル後のファイルが入る
サンプルプログラムの全体像
https://gyazo.com/819c31163bbccdaa70e11e465a7bf65b
プログラム全体の流れ
https://gyazo.com/a8685b2103fb36837abb29bb1ddda138
SGX SDKの詳細はだいぶ端折っているので注意
処理の大半をEnclave側に寄せている
Enclave側からOCallを2回呼んでいる
EDLの定義
まずはApp(非Enclave)とEnclaveのやりとりがどうなっているか見てみる。
ECall一つとOCall二つ、主な処理はECallのverify関数で行う、verify関数からsgx_init_quoteやsgx_get_quote関数を直接呼べないのでそれぞれOCallを介して呼ぶ
code: Enclave.edl
enclave
{
... importは省略 ...
trusted
{
/* ECall関数、main関数から呼び出す */
public sgx_status_t verify(sgx_quote_sign_type_t quote_type);
};
untrusted
{
/* この関数の中でsgx_init_quote関数を使用する */
sgx_status_t ocall_sgx_init_quote(
out sgx_target_info_t *ret_ti, out sgx_epid_group_id_t *ret_gid );
/* この関数の中でsgx_calc_quote_sizeとsgx_get_quote関数を使用する */
sgx_status_t ocall_get_quote(
uint32_t sigrl_len,
sgx_quote_sign_type_t quote_type,
in sgx_quote_nonce_t *p_nonce, out sgx_report_t *p_qe_report, uint32_t maxlen,
out uint32_t* p_quote_len );
};
};
今回使用するSGX SDKの関数
以下使用する順に列挙する
sgx_init_quote
Quoteの初期化をする関数、主にEPIDの取得をしている。AEServicesProviderインスタンスを介してAE(QE -> PvE)にアクセスしている
Enclaveの情報をTargetInfo構造体のインスタンスとして取得できる
rsgx_create_report
TargetInfoからReportを生成する関数
sgx_calc_quote_size
Quoteのサイズを取得する関数、AEServicesProviderインスタンスを介してAE(QE)にアクセスしている
sgx_get_quote
ReportからQuoteデータを生成する関数、、AEServicesProviderインスタンスを介してAE(QE)にアクセスしている
rsgx_verify_report
Reportの検証をする関数
sgxで始まる関数はApp Enclaveからは呼び出せない(AEにアクセスが必要な関数や内部でシステムコールを呼ぶような関数はEnclave内では使えない)のでOCallを介して呼ぶ、rsgxで始まる関数はApp Enclaveから直接呼び出すことができる。
使用するSGX SDKの関数と構造体の関係
前出の関数5つに加え、RAのプログラムで使用するSGX SDKの構造体4つの関係
使用する構造体はsgx_target_info_t、sgx_report_t、sgx_quote_t、sgx_report_body_tの4つ
https://gyazo.com/84d3ba1b35caa597b1c9330bad785f97
SGX SDK関数とIASサービスの呼び出し順序
今回使用するSGX SDKの関数とIASサービスの呼び出す順序と生成されるオブジェクトの関係
図は並列っぽい表現になっているが実際は直列に実行している
都合上sgx_calc_quote_size関数は省略
一連の流れの最終成果物としてアテステーションレポート(attn_report)が取得できるので、それをアプリケーション側で検証する
https://gyazo.com/d68e06f7b51622ff555f15d31972d0fd
Appのプログラム
プログラムのエントリーポイントであるmain関数の実装から。main関数ではEnclaveの初期化処理とECall関数を呼び出している。
code: main.rs
use sgx_types::*;
use sgx_urts::SgxEnclave;
fn init_enclave() -> SgxResult<SgxEnclave> {
... Enclaveの初期化処理、省略 ...
}
extern "C" {
fn verify(
eid: sgx_enclave_id_t,
retval: *mut sgx_status_t,
sign_type: sgx_quote_sign_type_t,
) -> sgx_status_t;
}
fn main() {
let enclave = match init_enclave() {
... エラー処理は省略 ...
};
let mut retval = sgx_status_t::SGX_SUCCESS;
let sign_type = sgx_quote_sign_type_t::SGX_LINKABLE_SIGNATURE;
// ECallの呼び出し
let result = unsafe { verify(enclave.geteid(), &mut retval, sign_type) };
... エラー処理は省略 ...
println!("+ remote attestation success..."); enclave.destroy();
}
OCall関数の実装は以下
SGX SDKの関数の呼び出し、ポインタ引数の値が更新される
OCallで使用するSGX SDKの関数はAEにアクセスが必要な関数なのでApp Enclaveから直接呼び出すことができない
code: main.rs
pub extern "C" fn ocall_sgx_init_quote(
ret_ti: *mut sgx_target_info_t,
ret_gid: *mut sgx_epid_group_id_t,
) -> sgx_status_t {
// sgx_init_quote関数の呼び出し
unsafe { sgx_init_quote(ret_ti, ret_gid) }
}
pub extern "C" fn ocall_get_quote(
p_sigrl: *const u8,
sigrl_len: u32,
p_report: *const sgx_report_t,
quote_type: sgx_quote_sign_type_t,
p_spid: *const sgx_spid_t,
p_nonce: *const sgx_quote_nonce_t,
p_qe_report: *mut sgx_report_t,
p_quote: *mut u8,
_maxlen: u32,
p_quote_len: *mut u32,
) -> sgx_status_t {
// sgx_calc_quote_size関数の呼び出し
let mut real_quote_len: u32 = 0;
let ret = unsafe { sgx_calc_quote_size(p_sigrl, sigrl_len, &mut real_quote_len as *mut u32) };
if ret != sgx_status_t::SGX_SUCCESS {
println!("sgx_calc_quote_size returned {}", ret);
return ret;
}
unsafe {
*p_quote_len = real_quote_len;
}
// sgx_get_quote関数の呼び出し
let ret = unsafe {
sgx_get_quote(
p_report,
quote_type,
p_spid,
p_nonce,
p_sigrl,
sigrl_len,
p_qe_report,
p_quote as *mut sgx_quote_t,
real_quote_len,
)
};
ret
}
Enclaveのプログラム
Enclaveの処理はverify関数から始まる。流れとしては以下
環境変数からIAS_KEYとSPIDを取得する
Intelの開発者登録をして取得したIAS_KEY(SUB_KEY)とSPIDをプログラム実行時に環境変数としてセットすることを想定している
アテステーションレポート作成関数の呼び出し
処理の大半はこの関数にある
アテステーションレポートのIntel署名を検証する
Intelのサーバから返ってきたデータが本物か検証する
アテステーションレポートからQuoteを取り出す
code: lib.rs
pub extern "C" fn verify(sign_type: sgx_quote_sign_type_t) -> sgx_status_t {
println!("verify started");
// 環境変数を取得する(IAS_KEYとSPID)
let ias_key = env::var("IAS_KEY").expect("IAS_KEY is not set");
let spid_env = env::var("SPID").expect("SPID is not set");
let spid = decode_spid(&spid_env);
// アテステーションレポートを作成する
let (attn_report, sig, cert) =
match create_attestation_report(&ias_key, spid, sign_type) {
... エラー処理は省略 ...
};
// アテステーションレポートのIntelの署名を検証する
match verify_intel_sign(attn_report.clone(), sig, cert) {
... エラー処理は省略 ...
};
// アテステーションレポートからQuoteを取り出す
let sgx_quote = match get_quote_from_attn_report(attn_report) {
Ok(r) => r,
Err(e) => return e,
};
// Quoteの内容を表示する
unsafe {
println!("sgx quote version = {}", sgx_quote.version);
println!("sgx quote signature type = {}", sgx_quote.sign_type);
println!(
"sgx quote report_data = {}",
sgx_quote
.report_body
.report_data
.d
.iter()
.map(|c| format!("{:02x}", c))
.collect::<String>()
);
println!(
"sgx quote mr_enclave = {}",
sgx_quote
.report_body
.mr_enclave
.m
.iter()
.map(|c| format!("{:02x}", c))
.collect::<String>()
);
println!(
"sgx quote mr_signer = {}",
sgx_quote
.report_body
.mr_signer
.m
.iter()
.map(|c| format!("{:02x}", c))
.collect::<String>()
);
};
sgx_status_t::SGX_SUCCESS
}
アテステーションレポート作成をするcreate_attestation_report関数の処理を見てみる。流れは以下
Quoteの初期化
IntelからSigRLを取得する
レポートを生成する(アテステーションレポートとは別物なので注意)
Quoteを取得する
レポートはQuoteの一部
レポートの検証
IntelにQuoteを検証してもらう
検証結果=アテステーションレポート
Quoteの初期化とQuoteの取得はOCallを介して行う
code: lib.rs
fn create_attestation_report(
ias_key: &str,
spid: sgx_spid_t,
sign_type: sgx_quote_sign_type_t,
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), sgx_status_t> {
// Quoteの初期化処理、EPID Group IDを取得する
let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED;
let mut ti: sgx_target_info_t = sgx_target_info_t::default();
let mut eg: sgx_epid_group_id_t = sgx_epid_group_id_t::default();
let res = unsafe {
ocall_sgx_init_quote(
&mut rt as *mut sgx_status_t,
&mut ti as *mut sgx_target_info_t,
&mut eg as *mut sgx_epid_group_id_t,
)
};
... エラー処理は省略 ...
// IAS_KEYとEPID Group IDを引数にIASからSigRLを取得する
let eg_num = as_u32_le(&eg);
let sigrl_vec = match client::get_sigrl_from_intel(ias_key, eg_num) {
... エラー処理は省略 ...
};
let (p_sigrl, sigrl_len) = if sigrl_vec.len() == 0 {
(ptr::null(), 0)
} else {
(sigrl_vec.as_ptr(), sigrl_vec.len() as u32)
};
// report_dataの生成(今回は何もセットしない)
let report_data: sgx_report_data_t = sgx_report_data_t::default();
// reportの生成
let report = match rsgx_create_report(&ti, &report_data) {
Ok(r) => {
println!("Report creation => success {:?}", r.body.mr_signer.m);
r
}
Err(e) => {
println!("Report creation => failed {:?}", e);
return Err(e);
}
};
let mut quote_nonce = sgx_quote_nonce_t { rand: 0; 16 }; let mut os_rng = sgx_rand::SgxRng::new().unwrap();
os_rng.fill_bytes(&mut quote_nonce.rand);
let mut qe_report = sgx_report_t::default();
const RET_QUOTE_BUF_LEN: u32 = 2048;
let mut quote_len: u32 = 0;
// Quoteを取得する
let p_report = (&report) as *const sgx_report_t;
let p_spid = &spid as *const sgx_spid_t;
let p_nonce = "e_nonce as *const sgx_quote_nonce_t;
let p_qe_report = &mut qe_report as *mut sgx_report_t;
let p_quote = return_quote_buf.as_mut_ptr();
let p_quote_len = &mut quote_len as *mut u32;
let result = unsafe {
ocall_get_quote(
&mut rt as *mut sgx_status_t,
p_sigrl,
sigrl_len,
p_report,
sign_type,
p_spid,
p_nonce,
p_qe_report,
p_quote,
RET_QUOTE_BUF_LEN,
p_quote_len,
)
};
... エラー処理は省略 ...
// レポートの検証
match rsgx_verify_report(&qe_report) {
... エラー処理は省略 ...
}
// init_quote関数呼び出し時に取得したtiのmr_enclaveと
// get_quote関数呼び出し時に取得したqe_report.bodyのmr_enclaveが一致することを確認する
if ti.mr_enclave.m != qe_report.body.mr_enclave.m
|| ti.attributes.flags != qe_report.body.attributes.flags
|| ti.attributes.xfrm != qe_report.body.attributes.xfrm
{
println!("qe_report does not match current target_info!");
return Err(sgx_status_t::SGX_ERROR_UNEXPECTED);
}
// IAS_KEYとQuoteを引数にIASからデータの検証結果(アテステーションレポート)を取得する
match client::post_report_to_intel(ias_key, quote_vec) {
Ok(r) => Ok(r),
Err(e) => {
println!("client::post_report_to_intel failed with {:?}", e);
return Err(e);
}
}
}
アテステーションレポートのIntel署名を検証するverify_intel_sign関数の処理を見てみる
署名の検証にはwebpkiCrateを使う
署名の検証にはIntelのルートCAを使う
code: lib.rs
fn verify_intel_sign(
attn_report: Vec<u8>, // アテステーションレポート
sig: Vec<u8>,
cert: Vec<u8>,
) -> Result<(), sgx_status_t> {
let now = match webpki::Time::try_from(SystemTime::now()) {
... エラー処理は省略 ...
};
// IntelのRootCAを読み込む
let root_ca_raw = include_bytes!("../ca.crt");
let root_ca_pem = pem::parse(root_ca_raw).expect("failed to parse pem file.");
let root_ca = root_ca_pem.contents;
let mut root_store = rustls::RootCertStore::empty();
root_store
.add(&rustls::Certificate(root_ca.clone()))
.unwrap();
let trust_anchors: Vec<webpki::TrustAnchor> = root_store
.roots
.iter()
.map(|cert| cert.to_trust_anchor())
.collect();
let mut chain: Vec<&u8> = Vec::new(); chain.push(&root_ca);
let report_cert = webpki::EndEntityCert::from(&cert).unwrap();
// サーバ証明書の検証をする
match report_cert.verify_is_valid_tls_server_cert(
SUPPORTED_SIG_ALGS,
&webpki::TLSServerTrustAnchors(&trust_anchors),
&chain,
now,
) {
... エラー処理は省略 ...
};
// 署名の検証をする
match report_cert.verify_signature(&webpki::RSA_PKCS1_2048_8192_SHA256, &attn_report, &sig) {
Ok(_) => Ok(()),
Err(e) => {
println!("verify_signature failed with {:?}", e);
Err(sgx_status_t::SGX_ERROR_UNEXPECTED)
}
}
}
アテステーションレポートからQuoteを取り出すget_quote_from_attn_report関数の処理を見てみる。流れは以下
タイムスタンプが取得できるか検証する
APIのバージョンが想定しているバージョンかどうか検証する
Quoteステータスを検証する
Quote Bodyをsgx_quote_t構造体に変換する
code: lib.rs
fn get_quote_from_attn_report(attn_report: Vec<u8>) -> Result<sgx_quote_t, sgx_status_t> {
let attn_report: Value = serde_json::from_slice(&attn_report).unwrap();
// タイムスタンプが正しく取得できるか検証する
let time_fixed = time.clone() + "+0000";
println!("Time = {}", time_fixed);
} else {
println!("Failed to fetch timestamp from attestation report");
return Err(sgx_status_t::SGX_ERROR_UNEXPECTED);
}
// APIのバージョンが想定しているバージョンかどうか検証する
if let Value::String(version) = &attn_report"version" { if version != "4" {
return Err(sgx_status_t::SGX_ERROR_UNEXPECTED);
}
}
// Quoteステータスを検証する
match quote_status.as_ref() {
"OK" => (),
"GROUP_OUT_OF_DATE" | "GROUP_REVOKED" | "CONFIGURATION_NEEDED" => {
// (オプショナル) sgx_report_attestation_status関数を使って詳細な情報を取得することができる
... 省略 ...
} else {
println!("Failed to fetch platformInfoBlob from attestation report");
return Err(sgx_status_t::SGX_ERROR_UNEXPECTED);
}
}
_ => return Err(sgx_status_t::SGX_ERROR_UNEXPECTED),
}
} else {
println!("Failed to fetch isvEnclaveQuoteStatus from attestation report");
return Err(sgx_status_t::SGX_ERROR_UNEXPECTED);
}
// Quote Bodyをsgx_quote_t構造体に変換する
Value::String(quote_raw) => {
let quote = base64::decode("e_raw).unwrap();
let sgx_quote: sgx_quote_t = unsafe { ptr::read(quote.as_ptr() as *const _) };
Ok(sgx_quote)
}
_ => {
println!("Failed to fetch isvEnclaveQuoteBody from attestation report");
Err(sgx_status_t::SGX_ERROR_UNEXPECTED)
}
}
}
実行結果
今回のサンプルプログラムを実行すると以下が出力される
code: bash
sgx quote version = 2
sgx quote signature type = 1
sgx quote report_data = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
sgx quote mr_enclave = 030fa2faa63318d53002ca33ba47ef9b640bddfbd93b8613fe4fffe8e2cac1f1
sgx quote mr_signer = 83d719e77deaca1470f6baf62a4d774303c899db69020f9c70ee1dfc08c7ce9e
Quoteの検証をどこまでするか
IntelのAPIから最終的に取得したQuoteを使ってどこまで検証すべきかは開発者側に委ねられている
MRENCLAVEやMRSIGNERの値を使って他のEnclaveアプリケーションの検証(ローカルアテステーション)をするパターンが多そう
enclave.signed.soのビルド時にMRENCLAVEをファイルに保存してRA実行時にRAの結果と比較するとEnclaveの完全性検証ができる
アプリケーションの構成や実現したいことによって検証も変わる
この辺りが参考になりそう
Remote Attestationとアプリケーションのスケーラビリティ
Remote Attestationのプログラムは複雑で処理に時間がかかるためできればRAだけをアプリケーションから独立して実行させたい
MRENCLAVEを使った厳密な検証をするのであればアプリケーションにRAを組み込まないと検証できない
アプリケーションをスケールさせづらい
Anonifyは組み込み型で実装している
VM上にRA専用アプリを立てて複数のEnlaveアプリからアクセスする方式のほうがスケールさせやすい
検証にはMRSIGNERを使うことになる。MRENCLAVEを使った厳密な検証は諦めることになる
MRENCLAVEはビルド時のEnclaveイメージとコンフィグファイルからハッシュ値を算出するためプログラムが違うと値が変わる
MRENCLAVEの値はCPUに依存しないためマシンが違っても、プラグラムとコンフィグファイルが同じでsgx_signのバージョンが同じであれば同じ値を生成することができる
ひとつのVM上に複数のEnclaveアプリを配置するのであればBFT(Byzantine Fault Tolerance)的な仕組みがあったほうがいいのかなとか、Enclaveアプリケーション全般に言えることだがスケールに課題がありそう
RAのスケーラビリティについては、この記事がおすすめ
補足: Report Dataの活用方法
Report Bodyにユーザ定義のReport Dataをセットすることができる
Report Dataは64バイトまでセット可能
実際どう活用するか
Rust SGX SDKのサンプル
外部サービスからTLS接続をしてRAを実行するサンプルの事例
キーペアを生成する
Report Dataに公開鍵をセットしてIASに送信
IASから返ってきたアテステーションレポートにプロイベートキーを使って署名する
署名済みのデータをTLSの証明書として使用する
まとめ
Remote Attestation難しい
IntelのRAのドキュメント読んでもよくわからん
一方でIntel Attestation ServiceのAPIドキュメントはわかりやすかった
日本語情報無さすぎ
Rust SGX SDKのサンプルプログラムから読み解いていくのが良い
Architectural Enclavesの役割や動きについて知ることでRAの流れを理解できるようになった
英語情報の中では以下のページがわかりやすかった
Remote Attestationの流れを把握するのがとても大変でした。その中でRust SGX SDKのサンプルプログラムはとてもありがたい存在でした。正直プログラムを書いている時間よりRust SGX SDKやIntelのSGX SDKのコードを読んでいる時間のほうが長かったように思います。そのおかげでRAだけではなくArchitectural Enclavesやaesm_serviceについても知ることができました。RAの流れを理解して実装に落とすことでSGXに対する理解がより深まりました。(文責・藤田) Anonify解体新書 | 連載一覧(全8回)