В третьей части серии учебных материалов, посвященных расширениям Intel Software Guard Extensions (Intel SGX), мы поговорим о проектировании приложений, использующих Intel SGX. Мы используем принципы, изученные в первой части, и применим их к общей концепции примера приложения Tutorial Password Manager, которая была рассмотрена во второй части. Мы обсудим общую структуру приложения и влияние Intel SGX на нее; мы создадим модель классов, которая даст нам возможность спроектировать и интегрировать анклав.



Пока мы не будем писать код анклавов и интерфейсов анклава, но в этой части статьи предоставляется пример кода. Версия ядра приложения, не использующая Intel SGX, без пользовательского интерфейса, доступна для загрузки. Она сопровождается небольшой тестовой программой — консольным приложением на C# — и образцом файла хранилища паролей.

Проектирование для анклавов


Вот общий подход, который мы будем использовать при проектировании Tutorial Password Manager для Intel SGX:

  1. Выявление секретов приложения.
  2. Выявление поставщиков и потребителей этих секретов.
  3. Определение границы анклава.
  4. Приспособление компонентов приложения для анклава.

Выявление секретов приложения


Первый этап при проектировании приложения, использующего Intel SGX — выявление секретов приложения.

Секрет — это любая информация, которую не должны видеть и знать другие. Доступ к секрету должен быть только у пользователя или у приложения, для которого предназначен этот секрет; доступ не должен предоставляться другим пользователям и приложениям вне зависимости от их уровня прав. В число возможных секретов входят финансовые данные, медицинские карты, личные сведения, данные идентификации, лицензированное содержимое мультимедиа, пароли и ключи шифрования.

В Tutorial Password Manager некоторые элементы сразу считаются секретами, как показано в таблице 1.
________________________________________
Секрет
________________________________________
Пароли учетных записей пользователя
Имена пользователя учетных записей пользователя
Главный пароль или парольная фраза пользователя
Главный ключ хранилища паролей
Ключ шифрования базы данных учетной записи
________________________________________

Таблица 1. Предварительный список секретов приложения.

Это очевидные секреты, но мы расширим этот список: добавим в него всю информацию учетных записей пользователя, а не только имена пользователя. Пересмотренный лист показан в таблице 2.
________________________________________
Секрет
________________________________________
Пароли учетных записей пользователя
Информация об именах пользователя учетных записях пользователя
Главный пароль или парольная фраза пользователя
Главный ключ хранилища паролей
Ключ шифрования базы данных учетной записи
________________________________________

Таблица 2. Переработанный список секретов приложения.

Даже если пароли скрыты, информация учетной записи (например, названия служб, URL-адреса) представляет ценность для злоумышленников. Раскрытие этих данных в диспетчере паролей позволит злоумышленникам получить больше возможностей для взлома системы. В частности, располагая этими данными, они смогут осуществлять атаки, направленные непосредственно на службы, возможно, используя методы социальной инженерии или атаки на сброс пароля, чтобы получить доступ к учетной записи владельца, поскольку им известно, кого именно следует атаковать.

Выявление поставщиков и потребителей секретов приложения


После выявления секретов приложения следует определить их источники и места назначения.
В текущей версии Intel SGX код анклава не шифруется. Это означает, что любой пользователь, располагающий доступом к файлам приложения, может дизассемблировать и проанализировать их. Данные, открытые для просмотра, по определению не могут быть секретом. Это означает, что секреты нельзя статически компилировать в код анклава. Секреты приложения должны происходить извне анклавов и загружаться в анклавы при выполнении. В терминологии Intel SGX это называется предоставлением секретов в анклав.

Когда источник секрета находится в компоненте вне доверенной вычислительной базы (TCB), важно снизить уязвимость секрета для недоверенного кода. (Одна из основных причин важности удаленной аттестации для Intel SGX состоит в том, что благодаря ей поставщик службы может установить отношение доверия с приложением Intel SGX, а затем сформировать ключ шифрования, который можно использовать для передачи зашифрованных секретов в приложение, причем расшифровать их сможет только доверенный анклав в системе клиента.) Аналогичные меры предосторожности требуется соблюдать при экспорте секрета из анклава. Как правило, секреты приложения не следует отправлять в недоверенный код без предварительного шифрования внутри анклава.

К сожалению, в Tutorial Password Manager мы вынуждены отправлять секреты в анклав и из анклава, и в определенный момент эти секреты будут в виде обычного текста, без шифрования. Конечный пользователь будет вводить свои учетные данные и пароль с помощью клавиатуры или сенсорного экрана и вызывать их в будущем при необходимости. Пароли учетных записей должны отображаться на экране и даже копироваться в буфер обмена Windows* по требованию пользователя. Без этого приложение, которое должно быть диспетчером паролей, просто не будет работать нужным образом.

Это означает, что мы не можем полностью устранить уязвимые участки: мы можем только уменьшить их, причем потребуется какая-либо стратегия защиты секретов, когда они выходят за пределы анклава в незашифрованном виде.
Секрет Источник Место назначения
Пароли учетных записей пользователя Ввод пользователя*
Файл хранилища паролей
Пользовательский интерфейс*
Буфер обмена*
Файл хранилища паролей
Информация учетных записей пользователя Ввод пользователя*
Файл хранилища паролей
Пользовательский интерфейс*
Файл хранилища паролей
Главный пароль или парольная фраза пользователя Ввод пользователя Функция формирования ключа
Главный ключ хранилища паролей Функция формирования ключа Шифр ключа базы данных
Ключ шифрования хранилища паролей Формирование случайным образом
Файл хранилища паролей
Шифр хранилища паролей
Файл хранилища паролей

Таблица 3. Секреты приложения, их источники и места назначения. Возможные риски безопасности отмечены звездочкой (*).

В таблице 3 перечислены источники и места назначения секретов Tutorial Password Manager. Возможные проблемы — области, в которых секреты могут быть доступны для недоверенного кода, — обозначены звездочкой (*).

Определение границы анклава


После установления секретов следует очертить границу анклава. Прежде всего, рассмотрим поток данных секретов через основные компоненты приложения. Граница анклава должна:

  • Заключать в себе минимальный набор важнейших компонентов, работающих с секретами приложения.
  • Заключать внутри себя как можно больше секретов.
  • Сводить к минимуму взаимодействие с недоверенным кодом и количество зависимостей от него.

Потоки данных и выбранная граница анклава Tutorial Password Manager показаны на рис. 1.


Рисунок 1. Потоки данных секретов в Tutorial Password Manager.

Секреты приложений показаны в виде кружков, причем синие кружки — это секреты, существующие в виде обычного текста (без шифрования) на каком-либо этапе выполнения приложения, а зеленые кружки — секреты, зашифрованные приложением. Граница анклава очерчена вокруг процедур шифрования и дешифрования, функции формирования ключа (KDF) и генератора случайных чисел. Такое решение обеспечивает достижение сразу нескольких целей:

  1. Ключ базы данных/хранилища, используемый для шифрования некоторых секретов приложения (информация учетных записей и пароли), формируется внутри анклава и никогда не передается наружу в виде обычного текста.

  2. Главный ключ формируется из парольной фразы пользователя внутри анклава и используется для шифрования и расшифровки ключа базы данных/хранилища. Главный ключ является временным и никогда не передается за пределы анклава ни в какой форме.

  3. Ключ базы данных/хранилища, информация об учетных записях и пароли учетных записей шифруются внутри анклава при помощи ключей шифрования, скрытых от недоверенного кода (см. №1 и №2).

К сожалению, незашифрованные секреты будут пересекать границу анклава, этого невозможно избежать. На определенном этапе при работе Tutorial Password Manager пользователю придется ввести пароль с клавиатуры или скопировать пароль в буфер обмена Windows. Это небезопасные каналы, их невозможно поместить внутрь анклава, а эти операции необходимы для работы приложения. Потенциально здесь может возникнуть серьезная проблема, осложняемая решением создавать приложение на основе базы управляемого кода.

Защита секретов вне анклава


Не существует полных решений для защиты незашифрованных секретов снаружи анклава. Существуют лишь стратегиидля уменьшения уязвимости. Лучшее, что можно сделать — свести к минимуму время, в течение которого информация существует в уязвимой форме.
Вот общие рекомендации по обработке конфиденциальных данных в недоверенном коде:

  • По окончании работы с буферами данных заполняйте их нулями. Используйте такие функции как SecureZeroMemory (Windows) и memzero_explicit (Linux), которые гарантированно не будут оптимизированы компилятором.

  • Не используйте контейнеры библиотеки стандартных шаблонов C++ (STL) для хранения конфиденциальных данных. Контейнеры STL используют собственные алгоритмы управления памятью, из-за чего непросто убедиться в безопасной очистке памяти, распределенной объекту, после удаления этого объекта. (Для некоторых контейнеров можно решить эту проблему, используя настраиваемые распределители.)

  • При работе с управляемым кодом, таким как .NET, или с языками с автоматическим управлением памятью используйте типы хранилищ, специально предназначенные для хранения защищенных данных. Безопасность других типов хранилищ зависит от работы сборщика мусора и JIT-компиляции, такие хранилища невозможно очищать и освобождать по запросу (либо невозможно вообще).

  • Если необходимо поместить данные в буфер обмена, не забудьте очистить буфер через короткое время. В частности, не позволяйте данным оставаться в буфере после завершения работы приложения.

Для проекта Tutorial Password Manager нам придется работать и с собственным, и с управляемым кодом. В собственном коде мы выделим буферы wchar_t и char, а с помощью SecureZeroMemory очистим их перед высвобождением. В управляемом коде мы используем класс .NET SecureString.

При отправке SecureString в неуправляемый код мы будем использовать вспомогательные функции из System::Runtime::InteropServices для направления данных.

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();
}

При направлении данных в обратную сторону, из собственного кода в управляемый код, можно использовать два метода. Если объект 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. Буфер обмена — небезопасное место для хранения, к которому могут получить доступ другие пользователи. Поэтому Майкрософт не рекомендует размещать там конфиденциальные данные. При этом назначение диспетчера паролей состоит в том, чтобы дать пользователям возможность создавать стойкие пароли, которые им не потребуется запоминать. Также можно будет создавать длинные пароли, состоящие из случайного набора символов, которые было бы непросто вводить вручную. Буфер обмена предоставляет весьма необходимое удобство при определенной степени риска.

Для устранения этого риска нужно соблюдать дополнительные меры предосторожности. Прежде всего, нужно очищать буфер обмена при завершении работы приложения. Это делается в деструкторе одного из наших объектов собственного кода.

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();
}

Приспособление компонентов приложения для анклава


Итак, когда определены секреты и очерчены границы анклава, пора подумать о структуре приложения с учетом анклава. Существуют строгие ограничения действий, допустимых внутри анклава. Эти ограничения определяют, какие компоненты могут находиться внутри анклава, какие — снаружи, а какие может потребоваться разделить на две части при преобразовании существующих приложений.

Наиболее важное ограничение, затрагивающее Tutorial Password Manager, состоит в том, что анклавы не могут выполнять операции ввода-вывода. Анклав не может получать текст с клавиатуры и выводить данные на экран, поэтому все секреты — пароли и данные учетных записей — необходимо вводить в анклав и выводить из него. Кроме того, анклав не может читать файл хранилища и записывать данные в него: компоненты, анализирующие файл хранилища, должны быть отделены от компонентов, выполняющих физические операции ввода-вывода. Это означает, что потребуется переносить через границы анклава не только секреты: потребуется переносить и содержимое файла.


Рисунок 2. Схема классов в Tutorial Password Manager.

На рис. 2 показана упрощенная схема классов для ядра приложения (кроме пользовательского интерфейса), включая классы, играющие роль источника и места назначения для секретов. Обратите внимание, что класс PasswordManagerCore считается источником и местом назначения для секретов, на этой схеме он взаимодействует с графическим пользовательским интерфейсом ради простоты. В таблице 4 приводится краткое описание всех классов и их назначения.
Класс Тип Функция
PasswordManagerCore Управляемый Взаимодействие с графическим пользовательским интерфейсом C# и сбор данных для уровня собственного кода.
PasswordManagerCoreNative Собственный код, не доверенный Взаимодействие с классом PasswordManagerCore. Также отвечает за преобразование между Юникодом и многобайтовыми символьными данными (это будет подробнее рассмотрено в части 4).
VaultFile Управляемый Чтение файла хранилища и запись в него.
Хранилище Собственный код, анклав Сохранение данных хранилища паролей в членах AccountRecord. Десериализация файла хранилища при чтении, повторная сериализация для записи.
AccountRecord Собственный код, анклав Сохранение информации учетной записи и паролей для всех учетных записей в хранилище паролей пользователя.
Crypto Собственный код, анклав Выполнение функций шифрования.
DRNG Собственный код, анклав Интерфейс генератора случайных чисел.
Таблица 4. Описания классов.

Обратите внимание, что обработка файла хранилища разделена на две части: одна часть посвящена операциям ввода-вывода на физическом уровне, другая сохраняет содержимое после чтения и анализа. Также потребовалось добавить методы сериализации и десериализации к объекту Vault в качестве промежуточных источников и мест назначения секретов. Это необходимо, так как класс VaultFile не имеет никакой информации о структуре файла хранилища, поскольку для этого требовался бы доступ к функциям шифрования, находящимся внутри анклава.

Мы также соединили класс PasswordManagerCoreNative с классом Vault пунктирной линией. Как вы, возможно, помните из второй части учебного курса, анклавы могут быть связаны только с функциями C. Эти два класса C++ не могут напрямую обмениваться данными друг с другом: им требуется посредник, который обозначается блоком «Функции моста».

Ветвь кода без использования Intel Software Guard Extensions


Схема на рис. 2 относится к ветви кода Intel SGX. Класс PasswordManagerCoreNative не может связываться напрямую с классом Vault, поскольку последний находится внутри анклава. Но в ветви кода без использования Intel SGX такого ограничения нет: PasswordManagerCoreNative может напрямую содержать член класса Vault. Это единственное упрощение, которое мы сделаем в приложении, не использующем Intel SGX. Чтобы упростить интеграцию анклава, в ветви кода без использования анклава обработка анклава будет выделена в классы Vault и VaultFile.

Еще одно важное различие между ветвями кода состоит в том, что функции шифрования в коде, использующем Intel SGX, берутся из Intel SGX SDK. Код, не использующий Intel SGX, не может содержать эти функции, поэтому они берутся из API Microsoft Cryptography: Next Generation* (CNG). Это означает, что потребуется поддерживать две разные копии класса Crypto: одну для использования в анклавах, другую для использования в недоверенном пространстве. (То же самое придется сделать и с другими классами; это будет описано в пятой части.)

Пример кода


Как уже было сказано выше, в этой части предоставляется пример кода для загрузки. Прилагаемый архив включает исходный код основной DLL-библиотеки Tutorial Password Manager до интеграции с анклавом. Другими словами, это версия ядра приложения, не использующая Intel SGX.

Пользовательский интерфейс отсутствует, но мы добавили простейшее тестовое приложение на C#, выполняющее последовательность тестовых операций. Оно выполняет два набора тестов: один создает новый файл хранилища и выполняет различные операции с ним, другой действует с эталонным файлом хранилища, входящим в состав дистрибутива исходного кода. На момент написания этой статьи тестовое приложение требует, чтобы тестовое хранилище находилось в папке Documents, но при необходимости можно изменить расположение в классе TestSetup.

Этот исходный код был разработан в Microsoft Visual Studio* Professional 2013 согласно требованиям, указанным во вступлении в эту серию учебных руководств. На данном этапе Intel SGX SDK не требуется, но понадобится система, поддерживающая технологию защиты данных Intel с безопасным ключом.

В дальнейших выпусках


В четвертой части учебного руководства мы разработаем анклав и функции моста. Следите за новостями!
Поделиться с друзьями
-->

Комментарии (0)