Введение
Летом 2015 года перед нами, разработчиками «Кода безопасности», встала задача реализации защищенного хранилища под Android с шифрованием по стандартам, признанным российским законодательством. До этого у нас уже было решение на планшете, к которому имелись исходники Android. И это позволило нам выпустить обновленное ядерное шифрование (dm-crypt) под поддержку ГОСТ 89, добавить в /system/lib ГОСТ-библиотеки, пропатчить cryptofs подсистему демона vold. В итоге в нашем распоряжении оказалось решение, которое подходило лишь для определенной модели планшета, и не являлось универсальным. Узнав, что в Android версии 4.4 (API уровня 19) появился API, позволяющий осуществлять доступ к данным после регистрации и реализации своего кастомного DocumentsProvider, мы решили создать решение, использующее GOST-шифрование в userspace с использованием данного API, которое не зависело бы от модели устройства.
Для тех, кто заитересовался — добро пожаловать под кат.
Как в Android данные шифруются
Кратко опишу процесс шифрования Android-устройства. Активируется шифрование следующим образом: Settings → Security → Encrypt tablet/phone, реализуется средствами linux kernel → dev-mapper → dm-crypt. При активации данной функциональности устройство запросит пароль, а также попросит сохранить и зашифровать данные из /data (или просто удалит все данные по желанию пользователя). После перезагрузки устройства появится окно ввода пароля, в системных настройках переменная ro.crypto.state будет установлена в состояние encrypted.
Стандартное шифрование разделов в Android базируется на модели FDE (full-disk encryption). Модель является обычной подсистемой ядра Linux – device mapper и обеспечивает прозрачное шифрование. Доступ к данным (например, к разделу /data) предоставляется через виртуальное блочное устройство /dev/block/dm-0, которое каждый io-request шифрует или расшифровывает в режиме AES-CBC на 128-битном секторном ключе, который вычисляется от IV (инициализационного вектора).
Рис. 1: Логическая схема шифрования Android
На схеме представлена последовательность запроса Pin от ключевого контейнера и установки master-key через syscall ioctl в ядерную часть dm-crypt.
Access framework (SAF)
Для применения приложения в java-space мы используем Storage Access Framework (SAF), или платформу доступа к хранилищу. На сайте developer.android.com довольно подробно описаны все свойства и нюансы использования этой платформы, здесь мы отметим лишь основные моменты.
Итак, SAF появилась в Android версии 4.4 (API уровня 19) и она облегчает пользователям поиск и открытие документов, изображений и других файлов в хранилищах всех поставщиков. Стать поставщиком этих файлов мы можем, реализовав класс DocumentsProvider и использовав несколько его методов (об этом ниже).
Вся платформа SAF состоит из следующих элементов:
1) «Клиент» – приложение, которое хочет получить доступ к файлу или создать новый файл. Для всей Android-системы это возможно с помощью интентов с флагами ACTION_OPEN_DOCUMENT и ACTION_CREATE_DOCUMENT соответственно.
2) «Поставщик документов» – наше приложение, которое, как упоминалось выше, является подклассом класса DocumentsProvider и реализует доступ к файлам через API (об этом далее).
3) «Элемент выбора» – системный пользовательский интерфейс, обеспечивающий пользователям доступ к файлам всех поставщиков, удовлетворяющих критериям поиска.
Теперь подробнее. Поставщик документов предоставляет один или несколько корневых каталогов, являющихся отправными точками при обходе дерева документов. Каждый корневой каталог имеет уникальный идентификатор COLUMN_ROOT_ID и указывает на документ (каталог), представляющий содержимое на уровне ниже корневого. Корневые каталоги динамичны по своей конструкции, что обеспечивает поддержку различных вариантов использования: нескольких учетных записей, временных хранилищ на USB-накопителях, возможности входа/выхода в систему/из системы.
Каждый сервер хранилища показывает отдельные файлы и каталоги, ссылаясь на них с помощью уникального идентификатора COLUMN_DOCUMENT_ID (в нашем случае, это полный путь от корня файловой системы до самого файла). Идентификаторы документов должны быть уникальными и не меняться после присвоения, поскольку они используются для выдачи постоянных URI, не зависящих от перезагрузки устройства.
Документ – это либо открываемый файл (имеющий конкретный MIME-тип), либо каталог, содержащий другие документы (с MIME-типом MIME_TYPE_DIR). Каждый документ может иметь различные свойства, описываемые флагами COLUMN_FLAGS, такими как FLAG_SUPPORTS_WRITE, FLAG_SUPPORTS_DELETE и FLAG_SUPPORTS_THUMBNAIL.
Рис. 2: Модель данных поставщика документов
Модель данных поставщика документов основана на традиционной файловой иерархии. Однако физический способ хранения данных остается на усмотрение разработчика при условии, что к ним можно обращаться через API-интерфейс DocumentsProvider. Например, можно использовать для данных облачное хранилище на основе тегов.
Обратите внимание на следующий рисунок:
Рис. 3: Модель взаимодействия поставщика документов и клиента
На платформе SAF поставщики и клиенты не взаимодействуют напрямую. Клиент запрашивает разрешение на взаимодействие с файлами (на их чтение, редактирование, создание или удаление).
Взаимодействие начинается, когда приложение (в нашем примере – обрабатывающее фотографии) активирует намерение ACTION_OPEN_DOCUMENT или ACTION_CREATE_DOCUMENT. Намерение может включать в себя фильтры для уточнения критериев, например, «предоставить открываемые файлы с MIME-типом image».
Когда намерение срабатывает, системный элемент выбора переходит к каждому зарегистрированному поставщику и показывает пользователю корневые каталоги с контентом, соответствующим запросу. Элемент выбора предоставляет пользователю стандартный интерфейс, даже если поставщики документов значительно различаются.
В Android версии 4.3 и ниже приложение для получения файла от другого приложения должно активировать намерение, например, ACTION_PICK или ACTION_GET_CONTENT. После этого пользователь должен выбрать одно из приложений, а оно должно предоставить пользователю интерфейс, с помощью которого он сможет выбирать и получать файлы.
Рис. 4: Диалог выбора поставщика документов
Рис. 5: Диалог выбора получателя документа
Регистрация как document provider
Следующим шагом в разработке собственного поставщика документов является создание подкласса абстрактного класса DocumentsProvider. Как минимум необходимо реализовать следующие методы:
queryRoots ()
queryChildDocuments ()
queryDocument ()
OpenDocument ()
Реализация данных методов строго обязательна, однако для реализации полнофункционального приложения придется реализовать и другие. Подробности приводятся в описании класса DocumentsProvider.
JNI
Следующая логическая часть – это JNI-реализация прослойки между нашей реализацией SAF и открытой библиотекой файловой системы для встраиваемых систем FATFS. Последняя была пропатчена на чтение-запись на файл (в нашем случае он выступает томом) поблочно. Таким образом, на IO-запрос мы получаем номер сектора и количество секторов, отсюда можно вычислить IV и секторный ключ, а затем зашифровать и расшифровать конкретный блок на файле. В итоге у нас получается аналог dm-crypt в userspace.
Рис. 6: Проброс методов SAF в нативную часть и обратно
Далее мы реализовали методы для работы с томами и файлами. Для работы с томами используем следующие функции:
Java_com_securitycode_fatfslib_FatFs_createVolume (...);
Создает файл тома через библиотеку файловой системы и создает внутри этого файла пустую шифрованную FS.
Рис. 7: Создание защищенного хранилища
Java_com_securitycode_fatfslib_FatFs_getVolumeFreeSpace (...);
Передав в функцию определенный том, мы получаем количество свободного места в этом томе.
Java_com_securitycode_fatfslib_FatFs_mount (...);
При монтировании тома мы устанавливаем мастер-ключ, с помощью которого будет происходить шифрование-расшифрование блоков FS.
Рис.8: Монтирование защищенного хранилища
Java_com_securitycode_fatfslib_FatFs_unMount (...);
Очистка ресурсов и завершение операций ввода-вывода.
Для работы с файлами:
Java_com_securitycode_fatfslib_FatFs_createFile (...);
На нашей файловой системе создается объект пустого файла с заданными размером и типом (необходим для SAF).
Java_com_securitycode_fatfslib_FatFs_getFile (...);
Данная функция необходима для SAF-метода queryDocument (...).
В функции мы возвращаем id файла, его тип, корневой узел, содержащий текущий файл и имя файла.
Java_com_securitycode_fatfslib_FatFs_readFile (...);
В данной функции создается Java-stream, который используется в SAF и в качестве URI передается потребителю.
Java_com_securitycode_fatfslib_FatFs_writeFile (...);
Аналогично предыдущей функции, только в данном случае потребителем становимся мы.
Рис.9: Общий экран приложения
Рис.10: Содержимое хранилища
Заключение
В результате всех вышеперечисленных действий мы получили криптодиск с поддержкой ГОСТ-шифрования и электронной подписи в userspace.
Будем рады ответить на ваши вопросы и более подробно рассказать обо всех этапах проекта.
Источники:
1) Nikolay Elenkov. Android Security Internals, Chapter 10: Device Security.
2) Шифрование диска в Android
source.android.com/security/encryption
3) Documents Provider и SAF
developer.android.com/intl/ru/guide/topics/providers/document-provider.html
4) FatFs — Generic FAT File System Module
elm-chan.org/fsw/ff/00index_e.html
Автор: Код Безопасности