CPU脆弱性についての論文を読んでいると必ずと言っていいほど出てくるIntel SGX(Software Guard Extension)。 なんとなく秘匿データを扱うためのIntelのハードウェア機構なんだろうなと思っていたけど、あまり知ったかぶりをしているのもよくないのでチュートリアルを読んでみることにした。
前回に続いて、今回はTutorialのPart-3だ。いよいよPassword Managerの実装に入っていく。まずはクラス設計から、どこに境界を作成するのかについて考えていく。
Intel® Software Guard Extensions Part-3: アプリケーションのデザイン
Part-3では、Intel® Software Guard Extensions (Intel® SGX)を使ってどのようにしてアプリケーションを設計するかについて説明する。Part-1ではSGXの考え方について説明し、Part-2ではTutorial Password Managerについて概要を説明した。ここでは、Intel SGXを使ったアプリケーションのおおざっぱな構成について概観enclaveを使用したデザインの設計に必要なクラスモデルを作成する。
チュートリアルの一覧については、Introducing the Intel® Software Guard Extensions Tutorial Seriesで見ることができる。
enclaveおよびenclaveインタフェースを使用したアプリケーションのコーディングはまだ行っていないが、ソースコードを提供している。ユーザインタフェースの無い非Intel SGXのアプリケーションコアはダウンロードすることができる。これは小さなテストプログラムであり、C#で記述されたコンソールで動作するプログラムと、サンプルのパスワードファイルで構成されている。
Enclaveのデザイン
Intel SGXを使用したTutorial Password Managerの設計では、以下の一般的なアプローチを取る。
- アプリケーションの秘密情報を決める。
- 秘密情報の提供者と使用者を決める。
- enclaveの境界を決める。
- enclaveのためのアプリケーションコンポーネントを仕立てる。
アプリケーションの秘密情報を決める
Intel SGXを用いたプリケーションを設計する最初のステップは、アプリケーションの秘密情報を決め絵うことである。
秘密情報は、他人に知られてはならない情報というだけの意味ではない。秘密情報にアクセスすることを目的とするユーザもしくはアプリケーションのみが参照することができ、特権レベルに関係なく他人もしくは他のアプリケーションに秘密情報が開示されるべきではない。秘密情報として可能性のあるものは金融情報、医療記録、個人ID、IDデータ、ライセンスメディアのコンテンツ、パスワード、暗号鍵などを含む。
Tutorial Password Managerでは、以下の表1に示すようなアイテムを秘密情報として即時に識別可能なものである(xxx)。
上記の表を拡張して、ログインだけでなくすべてのユーザのアカウント情報を含めることでこのリストを拡張する。修正されたリストを表2に示す。
パスワードが漏れなくてもサービスの名前やURLなどのアカウントの情報は攻撃者にとって有用な情報である。パスワードマネージャ内のデータが漏洩し悪用される恐れがある。漏洩したデータはサービスそのものの攻撃に使用されるかもしれないし、ターゲットが決まっているため、アカウントを乗っ取るためのソーシャルエンジニアリングやパスワードのリセット攻撃に使用されるかもしれない。
アプリケーションの秘密情報の提供者と使用者を決める
アプリケーションの秘密情報を決めると、次のステップは情報の出元と出先を決めす。
現在のIntel SGXのバージョンでは enclaveのコードは暗号化されておらず、つまりアプリケーションのファイルにアクセスできる任意のユーザはそのファイルをディスアセンブリし検査することができる。ファイルが検査されてしまうと何も秘密にすることができず、つまり秘密情報はenclaveのコードに静的にコンパイルしてはならないということを意味する。アプリケーションの秘密情報はenclaveの外部に配置しなければならず、ランタイム時にロードされるものとする。Intel SGXの用語では、enclaveに秘密情報をプロビジョニングすると言う。
秘密情報がTrusted Compute Base(TCB)コンポーネントの外側で作成される場合、Untrustedなコードに対して秘密情報を最小限に提供することが重要である。(Remote AttestationがIntel SGXにとって有用なコンポーネントである理由は、Remote Attestationはサービスの提供者がIntel SGXアプリケーションとの信頼される関係を構築することが許されることと、クライアントシステム上の信頼されたenclaveに対して暗号化された秘密情報をプロビジョニングするために使用される暗号鍵を入手することができるxxx)。秘密情報がenclaveよりも外に持ち出される時も、同様のケアを行わなければならない。一般的なルールとして、アプリケーションの秘密情報は、enclave内で暗号化されずに信頼されないコードに送るべきではない。
残念なことに、Tutorial Password Managerアプリケーションでは、秘密情報をenclaveよりも外に転送する必要があり、これらの秘密情報はいくつかのポイントで明確なテキストとして存在する。エンドユーザはアカウントの情報やパスワードをキーボードもしくはタッチスクリーン越しに入力し、必要ならばWindowsのクリップボードにコピーする。パスワードマネージャにとって、利便性を上げるために必要なコア機能である。
つまり、すべての攻撃海面を完全に除去することは不可能だ:ただし、最小化することはでき、秘密情報をenclaveの外側に取り出してプレインテキストで扱う場合の戦略を作成する必要がある。
秘密情報 | Source | Destination |
---|---|---|
ユーザのアカウントパスワード | ユーザの入力*・パスワード金庫ファイル | ユーザインタフェース*・クリップボード*・パスワード金庫ファイル |
ユーザのアカウント情報 | ユーザの入力*・パスワード金庫ファイル | ユーザインタフェース*・パスワード金庫ファイル* |
ユーザのマスタパスワード・パスフレーズ | ユーザの入力 | 鍵生成関数 |
マスター鍵パスワード金庫 | 鍵生成関数 | データベース鍵暗号 |
パスワードデータベースのための暗号鍵 | ランダム生成・パスワード金庫 | パスワード金庫crypto・パスワード金庫ファイル |
表3. アプリケーションの秘密情報についてsourceとdestination。潜在的なリスクはアスタリスク(*)で表示してある。
表3では、Tutorial Password Managerの秘密情報のsourceとdestinationを示している。潜在的な問題ー秘密情報が、信頼されていないコードにより漏洩してしまう可能性のある領域についてはアスタリスク(*)で示している。
Enclaveの境界を決める
秘密情報を決めると、enclaveの境界を決める時間である。アプリケーションで取り扱われる秘密情報のデータフローを作成してみよう。enclaveの境界は、以下の条件を満たす必要がある。
- アプリケーションの秘密情報を操作するクリティカルなコンポーネントを含んでいなければならない。
- 機能を実現するための多くの秘密情報を完全に含んでいなければならない。
- 信頼されていないコードとの相互作用や依存関係は、最小限にしなければならない。
Tutorial Password Managerのデータフローと選択したenclaveの境界を図1に示す。
この図では、秘密情報は円で表現されており、青色の円はプレーンテキスト(暗号化されていない)での秘密情報、緑色の円はアプリケーションにより暗号化された秘密情報を示す。enclaveの境界は暗号化と複合化のルーチンで囲まれており、鍵生成関数(key derivation function:KDF)と乱数生成期が用意されている。
- データベース・金庫鍵は、アプリケーションの秘密情報(アカウント情報とパスワード)を暗号化するときに使用し、enclaveの内部で生成されクリアテキストとしてenclaveの外に流れることは絶対にない。
- マスター鍵はenclaveの中でユーザのパスフレーズから生成され、データベース・金庫鍵の暗号化と複合化に使用される。マスター鍵は短命で、どのような形でもenclaveの外に漏れることは絶対にない。
- データベース・金庫鍵・アカウント情報・アカウントパスワードはenclaveの内部で暗号鍵を使って暗号化され、信頼されないコード上で見えることはない(#1と#2を参照のこと)
残念なことに、暗号化されていない秘密情報をenclaveの境界を越えてやり取りすることは、シンプルり避けられないという問題が生じる。Tutorial Password Managerの実行では、ユーザはパスワードをキーボードから入力したり、パスワードをWindowsのクリップボードにコピーしたり留守。enclaveの内部で管理することのできないセキュアでないチャネルが存在し、アプリケーションの機能としてそれは必要不可欠である。このこと自体が非常に大きな問題でありアプリケーションを管理されたコードベースのトップでビルドする。
秘密情報をenclaveの外部で保護する
enclaveの外部で暗号化されていない秘密情報を保護する完全な方法は存在しないが、攻撃する境界を削減するための緩和手法は存在する。簡単に読み取ることのできるフォーム上に情報が存在する時間を可能な限り削減するのがベストである。
秘密情報を、信頼されていないコード上で取り扱う時の一般的なアドバイスを以下に示す。
- 処理を完了したときに、データバッファを0で埋める。
SecureZeroMemory
(Windows)のような関数やmemzero_explicit
(Linux)などの関数を使って、コンパイラに最適化されないことを保証させる。 - 秘密情報を格納するためにC++の標準的なtemplate library(STL)コンテナを使用しない。STLコンテナは独自のメモリ管理を行っており、オブジェクトに割り当てられたメモリが、そのオブジェクトが買いオフされたときに安全に解放されていることを保証することが難しい(いくつかのコンテナに対して、この問題を解決するためにカスタムアロケータを使用することはできる)。
- .NETや、自動メモリ管理の機能を持っているような言語を使用するときは、セキュアなデータを保持するために用意されている型を明示的に仕様sる。他の情方タイプは、がーべじコレクタやジャストインタイムコンパイルの慈悲があり、必要に応じて明確にクリアされたり解放されない場合がある。
- データをクリップボードに置いた場合、クリップボードを短い時間でクリアするべきである。時にアプリケーションが終了するときに、クリップボードの内容を消去すべきである。
Tutorial Password Managerプロジェクトでは、ネイティブなコードとマネージドコードを使用する。ネイティブなコードでは、wchar_t
とchar
のバッファに割り当て、SecureZeroMemory
関数を使用して、バッファの解放前にメモリをクリアする。マネージドなコードでは、.NETのSecureString
クラスを使用する。
SecureString
をマネージドではないコードに渡すためには、System::Runtime::InteropServices
を使用する。
01 using namespace System::Runtime::InteropServices; LPWSTR PasswordManagerCore::M_SecureString_to_LPWSTR(SecureString ^ss) { IntPtr wsp= IntPtr::Zero; if (!ss) return NULL; wsp = Marshal::SecureStringToGlobalAllocUnicode(ss); return (wchar_t *) wsp.ToPointer(); }
ネイティブなコードからマネージドなコードに変換するためには2つの手法がある。SecureString
なオブジェクトが既に存在している場合は、Clear
とAppendChar
メソッドを使ってwchar_t
の文字列を格納する。
password->Clear(); for (int i = 0; i < wpass_len; ++i) password->AppendChar(wpass[i]);
SecureString
小部栄久とを作成する場合は、SecureString
クラスのコンストラクタを使用してwchar_t
の文字列を格納する。
try { name = gcnew SecureString(wname, (int) wcslen(wname)); login = gcnew SecureString(wlogin, (int) wcslen(wlogin)); url = gcnew SecureString(wurl, (int) wcslen(wurl)); } catch (...) { rv = NL_STATUS_ALLOC; }
私たちのパスワードマネージは、Windowsのクリップボードにパスワードを転送することもサポートしている。クリップボードはインセキュアな格納スペースであり、他のユーザにアクセスされる可能性もあることからMicrosoftはクリップボードに秘密情報を格納すべきではないと言っている。パスワードマネージのポイントは、ユーザが強力なパスワード作成して覚えることができないようにすることである。長くてランダムな文字列で構成されたパスワードを生成して、ユーザが手で入力することが不可能なものを作るのである。クリップボードを使用しなければこれを使うことは難しく、リスクをとるほかない。
このリスクを緩和するためには、いくつかの他の注意事項を設定する。1つ目はアプリケーションが終了するときにクリップボードもクリアすることである。これは私たちのネイティブオブジェクトをクリア宇するデストラクタに以下のように記述することで達成できる。
PasswordManagerCoreNative::~PasswordManagerCoreNative(void) { if (!OpenClipboard(NULL)) return; EmptyClipboard(); CloseClipboard(); }
もう一つ、クリップボードのタイマを設ける。パスワードがクリップボードにコピーされると、15ぼ用達とクリップボードの中身がクリアされるような機能を追加する。タイマが既に動作している場合は、古いパスワードがクリアされる前に、新しいパスワードが配置されたということなので、タイマをキャンセルし再起動する。
void PasswordManagerCoreNative::start_clipboard_timer() { // Use the default Timer Queue // Stop any existing timer if (timer != NULL) DeleteTimerQueueTimer(NULL, timer, NULL); // Start a new timer if (!CreateTimerQueueTimer(&timer, NULL, (WAITORTIMERCALLBACK)clear_clipboard_proc, NULL, CLIPBOARD_CLEAR_SECS * 1000, 0, 0)) return; } static void CALLBACK clear_clipboard_proc(PVOID param, BOOLEAN fired) { if (!OpenClipboard(NULL)) return; EmptyClipboard(); CloseClipboard(); }
enclaveのためのアプリケーションコンポーネントを仕立てる
enclave境界と秘密情報について決まったら、enclaveを活用してアプリケーションを構築する時間だ。enclaveの中で行えることには重大な制限があり、これらの制限はenclaveの外部に委任数することになり、既存のアプリケーションを移植する際に、アプリケーションを2つに分割する必要も生じる。
Tutorial Password Managerの最も大きな制約は、enclaveはどのようなI/O操作も実行できないということである。enclaveはキーボードからの読み込みもディスプレイへの表示も行うことができず、すべての秘密情報ーパスワードやアカウント情報ーはenclaveの境界で取得されていなければならない。金庫ファイルからの読み書きも許されない: 金庫ファイルを読み出すコンポーネントは物理I/Oを操作するコンポーネントとは分離しなければならない。したがって、私たちはenclaveの境界での秘密情報の操作だけでなくファイルの操作についても考慮する必要がある。
図2. はアプリケーション子あの基本的なクラス図を示している(ユーザインタフェースは省略)。ここには秘密情報を処理するためのsourceとdestinationの役割も示されている。PasswordManagerCore
クラスは秘密情報のsourceとdestinationであり、ダイアグラム中のGUIとの相互やり取りの機能も含んでいる。表4. は各クラスの役割と目的を示している。
クラス | 型 | 機能 |
---|---|---|
PasswordManagerCore |
マネージド | C#グラフィカルインタフェース(GUI)とデータの整列・ネイティブレイヤとのインタラクト |
PasswordManagerCoreNative |
ネイティブ・Untrusted | PasswordManagerCore クラスとのやりとり。Unicodeとマルチバイト文字データとの変換も担当する(Part-4でより詳細を議論する) |
VaultFile |
マネージド | 金庫ファイルの読み書きを行う |
Vault |
ネイティブ・enclave | パスワード金庫データをAccountRecord メンバに格納する。読み込み時の金庫ファイルのDeserializeと、書き込み時のReserializeを行う。 |
AccountRecord |
ネイティブ・enclave | 各アカウントにおけるアカウント情報とパスワードを、ユーザのパスワード金庫に格納する |
Crypto |
ネイティブ・enclave | 暗号化関数を実行する |
DRNG |
ネイティブ・enclave | ランダム生成のインタフェース |
表4. クラスの説明
金庫ファイルを2つに分割しなければならないことに注意する: 1つは物理I/Oの処理、もう一つはその内容を読み込んで処理する。Vault
オブジェクトに対するシリアライズとデシリアライズの関数を追加しなければならない。VaultFile
クラスはvault
ファイルそのものの構成を知らないため、従ってenclave中に配置されている暗号化関数が必要である。
PasswordManagerCoreNative
クラスとVault
クラスに対して点線を引いている。Part-2で示したように、enclaveはC関数にのみリンクされる。ここでは2つのC++クラスが存在しているが、直接互いにやり取りすることができない: したがってBridge関数を使用して相互にやり取りを行う必要がある。
非Intel® Software Guard Extensionコードパス
図2はIntel SGX用のコードパスである。Vault
クラスはenclave内であるため、PasswordManagerCoreNative
クラスはVault
クラスと直接リンクすることはできない。非Intel SGXコードパスはこの関数を使用できないため、Microsoft Cryptograpy API: Next Generation(CNG)を使用する。つまり、Crypto
クラスの2つのコードを管理しなくてはならないことを意味する: 1つはenclave内に存在するものと、もう一つは信頼できない領域に存在しているものである(Part-5では、同様の処理をもう一つ別のクラスで実施しなければならない)。