Устранение неполадок с пакетом SDK для iOS
Оповещение, запрашивающее обновление, содержит не строки, а только ключи для них.
Это означает, что AppCenterDistributeResources.bundle объект не был добавлен в проект. Убедитесь, что файл удален в проект Xcode, и он отображается на этапе сборки целевого Copy Bundle Resources объекта приложения. Он должен появиться там, если вы добавили файл с помощью перетаскивания . Xcode делает это автоматически. Если файл отсутствует на этапе сборки, добавьте его, чтобы он компилировался в пакет приложения.
Если вы используете Cocoapods, он автоматически отвечает за ресурсы. Попробуйте переустановить модуль pod.
В консоли отображаются сообщения о том, что не удалось открыть базу данных
Начиная с версии 0.11.0 пакета SDK для iOS Центр приложений использует SQLite для сохранения журналов перед их отправкой в серверную часть. Если вы обобщаете приложение с собственной библиотекой SQLite, а не используете библиотеку, предоставленную ОС, в консоли [AppCenter] ERROR: -[MSACDBStorage executeSelectionQuery:]/147 Failed to open database могут появиться ошибки, подобные этой, и в серверной части не будут отображаться аналитические сведения или сведения о сбоях. Обновите пакет SDK до версии 0.13.0 или более поздней.
Распространение и обновления из приложения блокируют автоматические тесты пользовательского интерфейса
Если обновления в приложении включены, они заблокируют автоматические тесты пользовательского интерфейса. Процесс обновления будет пытаться пройти проверку подлинности в серверной части Центра приложений. Рекомендуется не включать распространение в Центре приложений для целевого объекта теста пользовательского интерфейса.
Почему пакет SDK распространяется как «статическая библиотека»
Основными целями разработки пакета SDK центра приложений являются минимальное влияние на приложение, использующий Центр приложений, и наличие модульного пакета SDK. Это приведет к распространению пакета SDK в виде нескольких динамически связанных общих библиотек.
Исторически сложилось так, что iOS не поддерживала динамические связанные общие библиотеки, но была добавлена в iOS 8, как описано в этой записи блога Лэндона Фуллера.
Однако Центр приложений распространяется как статически связанная общая библиотека, заключенная в «жирную» поддельную платформу. Это означает, что пакет SDK связан во время компиляции , а не во время запуска для повышения производительности. Загрузка нескольких динамически связанных общих библиотек занимает время.
Apple рекомендует оптимизировать запуск приложения, чтобы занять не более 400 мс в сеансе WWDC. Они специально рекомендуют статические общие библиотеки вместо динамических общих библиотек для достижения этой цели. Распространение пакета SDK центра приложений для iOS в виде статической общей библиотеки соответствует рекомендации Apple, чтобы обеспечить наилучшую производительность и минимальное влияние на приложение, включающее пакет SDK.
Чтобы узнать больше о статически связанных общих библиотеках и динамически связанных общих библиотеках, рекомендуем ознакомиться с общей документацией Apple по этой теме.
Почему двоичные файлы пакета SDK так велики? Меня беспокоит размер моего приложения
Двоичные файлы AppCenter распространяются как «жирные» платформы, содержащие срезы для всех архитектур iPhone и симулятора iPhone. Вот почему, например , AppCenter.framework составляет 10,5 МБ для скачивания.
Скомпилированный размер двоичных файлов пакета SDK будет гораздо меньше, чем .framework размер, добавляемый в приложение в Xcode. Кроме того, имейте в виду, что сборки выпуска также будут меньше, чем отладочные сборки.
Чтобы проиллюстрировать это, мы создали пустое приложение Objective-C с Xcode 9.2, добавили в приложение двоичные файлы Центра приложений и распространили сборки выпусков на iPhone 7 под управлением iOS 11.3.
Мы выполнили тесты, не включив Bitcode , и не использовали app Thinning. Вы можете использовать эти методы, чтобы еще больше уменьшить двоичный размер приложения.
Приведенные ниже числа могут отличаться и зависеть от параметров сборки, поэтому рассмотрим их как приблизительные. При этом добавление пакета SDK центра приложений в приложение оказывает минимальное влияние на размер двоичного файла приложения.
| Используемые модули Центра приложений | Экспортируемый размер IPA | Размер установки |
|---|---|---|
| Нет (пустое приложение) | 24 КБ | 132 КБ |
| Аналитика Центра приложений | 120 КБ | 377 КБ |
| Сбой Центра приложений | 239 КБ | 705 КБ |
| Распространение через Центр приложений | 163 КБ | 528 КБ |
| Все модули Центра приложений | 314 КБ | 930 КБ |
Защита значения секрета Центра приложений
— app_secret это идентификатор вашего приложения. Он должен знать, к какому приложению применяется трафик, и его нельзя использовать для получения или изменения существующих данных. Если ваш app_secret объект подвержен риску, самый большой риск заключается в отправке неверных данных в приложение, но это не повлияет на безопасность данных.
Чтобы получить конфиденциальные данные, необходимо предоставить маркер приложения или пользователя, который создается на стороне клиента. Невозможно обеспечить полную безопасность данных на стороне клиента.
Вы можете повысить безопасность приложения, используя переменную среды для внедрения секрета приложения в код. Таким образом, секрет не отображается в коде.
Почему сборка iOS завершается ошибкой «В цепочке ключей не удалось найти допустимые ключи подписывания кода iPhone»?
Это сообщение об ошибке возникает, если проекту требуются действительные учетные данные подписывания кода, но их не удается найти. Подписывание кода требуется для тестирования и развертывания на физических устройствах iOS; а также нерегламентированные & сборки App Store.
Это может произойти, если вы выполняете сборку из Visual Studio в Windows и пытаетесь выполнить сборку с помощью профиля распространения и сертификата, но у вас нет удаленного устройства или физического устройства, подключенного к узлу сборки Mac, выбранного в качестве целевого устройства. Если выбрано локальное устройство или устройство, подключенное к компьютеру с Windows, сборка не сможет найти сертификат распространения, даже если он установлен на компьютере Mac.
Подготовка устройств
Если вы еще не подготовили устройство iOS ранее, в следующем руководстве показано, как выполнить полный пошаговый процесс: Руководство по подготовке устройств.
Ошибка при использовании симулятора iOS
Эта проблема устранена в последних версиях Xamarin для Visual Studio. Тем не менее, если проблема возникает в последней версии программного обеспечения, создайте файл новой ошибки с полными сведениями о версиях и полным выводом журнала сборки.
В Xamarin.Visual Studio 3.11 произошла ошибка, из-за которой проект iOS в шаблоне Xamarin.Forms добавлял codesign Entitlements.plist в сборки симулятора; эффективно блокирует тестирование с помощью симулятора.
Как исправить
Эту проблему можно решить, удалив флаг из отладочных сборок в CSPROJ-файле. Это можно сделать следующим образом:
Ошибки в CSPROJ-файлах могут нарушить проект, поэтому рекомендуется создать резервную копию файлов перед попыткой.
- Щелкните правой кнопкой мыши проект iOS в области решения и выберите пункт Выгрузить проект.
- Щелкните проект правой кнопкой мыши еще раз и выберите Изменить [имя_проекта].csproj.
- Найдите debug PropertyGroups, они должны начинаться с флагов, которые выглядят следующим образом:
- Отладки:
- Выпуска:
- В каждой сборке, которая использует симулятор, удалите или закомментируйте следующее свойство:
Entitlements.plist - Перезагрузите проект, и вы сможете выполнить развертывание в симуляторе.
Next Steps
Чтобы получить дополнительную помощь, связаться с нами или если проблема остается даже после использования указанных выше сведений, сведения о способах связи, предложениях, а также о том, как при необходимости сообщить о новой ошибке, см. здесь.
Доверие к мобильным SDK

Недавняя история о бэкдоре в популярнейшей NPM-библиотеке заставила многих задуматься о том, насколько мы доверяем стороннему коду и как смело используем его в своих проектах (потенциально подставляя тем самым пользователей наших продуктов).
Но ещё за месяцы до того, как «гром грянул», Феликс Краус (известный мобильным разработчикам как создатель Fastlane) говорил на нашей конференции Mobius 2018 Piter о похожем: доверии мобильных разработчиков к сторонним SDK. Если мы скачиваем популярный SDK от известной компании, то вот там-то всё хорошо, или тоже что-то может пойти не так? Где тут есть вектор атаки и о чём нам стоит задумываться в связи с этим?
А теперь мы расшифровали и перевели его доклад — так что под катом можете хоть посмотреть оригинальное видео, хоть прочитать русскоязычную текстовую версию. Поскольку Краус занимается iOS-разработкой, все примеры приведены тоже из iOS, но Android-разработчики могут абстрагироваться от конкретных примеров и тоже задуматься.
Безопасность SDK
Посмотрев, какие SDK наиболее популярны в iOS-разработке сегодня, я решил исследовать, насколько они уязвимы по отношению к самым обыкновенным сетевым атакам. Целых 31% из них оказались потенциальными жертвами для простых атак man-in-the-middle, означающих, что хакер внедряет в SDK свой зловредный код.
Но в общем-то, стоит ли так пугаться? Что самое худшее он может сделать с вашим SDK? Настолько ли всё это серьёзно? Вы должны понимать, что SDK, включённый в bundle вашего приложения, запускается в его области видимости — то есть SDK получает доступ ко всему тому, чем оперирует ваше приложение. Если пользователь разрешает приложению доступ к геолокационным данным или, скажем, к фото, то эти данные также оказываются доступными и для SDK (не требуется никаких дополнительных разрешений). Другие данные, к которым возможен доступ из SDK: шифрованные данные iCloud, API-токены, а кроме того, все UIKit Views, содержащих массу разной информации. Если же хакер перехватывает SDK, имеющее доступ к такого рода данным, то последствия, как вы понимаете, могут быть достаточно серьёзными. При этом затронутыми окажутся одновременно все пользователи приложения.
Как именно это может произойти? Давайте сделаем шаг назад и поговорим о базовых сетевых вещах и о злоупотреблении ими.
Сеть
Предупрежу, что мои объяснения не будут на 100% точными и детальными. Моя цель — донести суть, поэтому я буду представлять всё в несколько упрощённом виде. Если вас заинтересуют подробности, то рекомендую вам заглянуть в мой блог либо исследовать тему самим.
Как вы помните, главное отличие протокола HTTPS от протокола HTTP — шифрование данных при передаче. Отсутствие шифрования у HTTP, по большому счёту, означает, что любой хост, находящийся внутри сети, при желании может свободно прослушивать и модифицировать любые передаваемые по ней пакеты; при этом проверить, была ли нарушена целостность пакета, не представляется возможным. Всё это справедливо и для публичных Wi-Fi сетей, и запароленных, и локальных Ethernet-сетей.
При использовании HTTPS хосты сети также могут прослушивать передаваемые пакеты, однако они не имеют возможности их вскрывать — видны только метаданные пакета, в частности, адреса хоста-отправителя и хоста-получателя. Кроме того, HTTPS предоставляет возможность верификации: получив пакет, вы можете проверить, подвергался ли он изменениям за время нахождения в пути.
Раньше всё работало следующим образом. Когда вы вводили в адресную строку «google.com» без указания протокола, браузер по умолчанию отсылал ваш запрос через протокол HTTP. Но поскольку сервер Google предпочитает взаимодействовать через HTTPS, в ответ от него вы получали новую ссылку (редирект) с префиксом «https://». Перед вами скрин Charles Proxy (инструмента мониторинга HTTP/HTTPS трафика), демонстрирующий это:

Однако сама новая ссылка высылалась через протокол HTTP. Несложно понять, что тут может пойти не так: и запрос, и ответ передаются по HTTP, а значит, можно, к примеру, перехватить пакет ответа и заменить в нём адрес location URL обратно на «http://». Этот простой вид атаки носит название «SSL-стрип». На сегодняшний день браузеры уже научились работать несколько иначе. Но понимание, что такое SSL-стрип, нам далее пригодится.
По временам учёбы вы можете помнить сетевую модель OSI. Я её воспринимал как что-то невыносимо скучное. Но позже обнаружил, что, как ни странно, модель OSI существует не просто так и даже может быть полезной.
Рассматривать её в деталях мы не будем. Главное, что надо понимать: всё состоит из нескольких слоёв, отвечающих за разные вещи и при этом находящихся в постоянном взаимодействии друг с другом.
Один из слоёв пытается определить, какой MAC-адрес соответствует определённому IP-адресу. Для этого выполняется специальный широковещательный запрос, фиксируется первое отреагировавшее устройство, и впоследствии пакеты отправляются ему.

Проблема в том, что хакер может откликаться на запрос быстрее: «да-да, шлите мне все пакеты». Это называют ARP-спуфингом или ARP cache poisoning. В таком случае схема вашего взаимодействия с интернетом превращается в такую:

Все пакеты теперь проходят через устройство хакера, и, если трафик не зашифрован, он сможет осуществлять и чтение, и запись. В случае с HTTPS возможности меньше, но можно проследить, к каким хостам вы обращаетесь.
Что интересно, фактически те же самые полномочия имеют интернет- и VPN-провайдеры. Они являются посредниками в вашем взаимодействии с интернетом и точно так же представляют потенциальную угрозу ARP-спуфинга.
В самом по себе подходе man-in-the-middle ничего нового нет. А вот как именно всё это применимо к мобильным SDK?
Мобильная специфика
CocoaPods — это стандартное средство управления зависимостями, применяемое в iOS-разработке. Использование CocoaPods для опенсорсного кода считается практически безопасным — обычно хостится на GitHub, обычно доступ по HTTPS или SSH. Тем не менее, мне удалось найти уязвимость, основанную на использовании HTTP-ссылок.
Дело в том, что CocoaPods даёт возможность установки SDK с закрытым исходным кодом, и от вас требуется просто указать URL. Нет проверки, что трафик будет зашифрованным, и многие SDK предлагают HTTP-адрес.
В связи с этим я направил разработчикам CocoaPods несколько пулл-реквестов, и вскоре они выполнили доработку. Теперь новые версии CocoaPods проверяют указываемую пользователем ссылку, и в случае, если она нешифрованная, отображают предупреждение. Так что мой совет: всегда обновляйте версии CocoaPods и не игнорируйте предупреждения.
Ещё интереснее рассмотреть то, как проходит установка неопенсорсных SDK не из CocoaPods. Возьмём, к примеру, платформу Localytics.

Страница docs.localytics.com не является шифрованной. Казалось бы, в данном случае этим можно и пренебречь, ведь это всего лишь документация. Но заметьте, что страница в числе прочего содержит ссылку на скачивание бинарников. Ссылка может быть и шифрованной, однако никакой безопасности в данном случае это не гарантирует: поскольку сама страница будет передаваться через HTTP, её можно перехватить и заменить в ней ссылку на нешифрованную. Об этой уязвимости разработчики localytics были поставлены в известность, и она уже устранена.
Можно поступить и иначе: не менять ссылку на HTTP, а оставить HTTPS, но подменить сам адрес. Обнаружить такое будет очень непросто. Посмотрите на данные две ссылки:

Одна из них принадлежит мне. Какая из них — от настоящих разработчиков, а какая нет? Попробуй пойми.
Практическая проверка
Дальше я решил проверить свои предположения, попробовав в реальности подменить содержимое SDK с помощью MITM-атаки. Оказалось, что и это не так сложно. Для построения и приведения в действие своей схемы мне потребовалось буквально несколько часов настройки простейших общедоступных инструментов.

Осуществлять перехват я поручил обычному Raspberry Pi. Включённый в локальную сеть, он мог прослушивать в ней трафик. В перехваченных же пакетах он должен был, во-первых, заменять все ссылки HTTPS на ссылки HTTP, а затем заменять все .zip-файлы Localytics iOS на файл hack.zip. Всё это оказалось просто и сработало на ура.

В полученном архиве появлялся файл trollface.jpg, а в файле Info.plist — строка «Modified by KrauseFx». Много ли требовалось для такой атаки? Всего лишь два условия:
- В вашу сеть сумели получить доступ (помните, что для интернет- и VPN-провайдеров это и вовсе не вопрос). К скольки сетям кофеен и гостиниц мы подключаемся?
- Инициируемая загрузка — нешифруемая.
Вы скажете «но я просто смотрю на значок Secure в браузере, значит, у меня всё будет ОК». И если находишься на сайте Amazon, уж там-то всё должно быть в порядке, так?
Предлагаю рассмотреть сайт Amazon. AWS Mobile SDK — их личный SDK, который они предоставляют разработчикам для взаимодействия с сервисом.

Значок «secure», известный сайт — ничего вроде бы не предвещает беды. Но увы — только на первый взгляд. Ссылка на скачивание SDK указана без префикса вообще (ни https://, ни http://). И при этом она должна увести пользователя на другой хост. Поэтому браузер переключится с HTTPS на HTTP. Как видите, и здесь загрузка SDK — нешифрованная! На данный момент уязвимость уже исправлена разработчиками Amazon, однако она действительно имела место быть.
Надо сказать, что разработка современных браузеров тоже ведётся не без внимания к вопросам безопасности. Например, если вы грузите страницу, используя HTTPS, и какая-нибудь одна картинка указана через ссылку-HTTP, то Google Chrome уведомит вас о так называемом «смешанном контенте». Но для загрузок такая мера безопасности не предусмотрена: браузеры не отслеживают, какой протокол срабатывает для указанной на странице ссылки на скачивание. Поэтому в рамках этого проекта я писал разработчикам браузеров с просьбой предусмотреть отслеживание смешанного контента и уведомление о нём пользователей.
Кража данных Apple ID
Теперь посмотрим на другую проблему. Пользователям iPhone должно быть знакомо такое регулярно всплывающее окно:

Слева вы видите оригинальный вариант iOS, справа — мою копию. О том, как несложно сымитировать подобное окно, я писал в своём блоге несколько месяцев назад. На то, чтобы воссоздать вид, ушло 20 минут.
iPhone достаточно часто запрашивает аутентификационные данные iCloud, причём для пользователя обычно остаётся неясным повод для запроса. Пользователи так привыкли к этому, что вводят пароль автоматически. Вопрос о том, кто запрашивает пароль — операционная система или приложение — просто не приходит в голову.
Если вы думаете, что есть сложность в том, чтобы получить адрес почты, к которому привязан Apple ID, то вы преувеличиваете: это можно сделать и через книгу контактов, и через контейнеры iCloud (при наличии доступа приложения к iCloud, о котором вы можете узнать из UserDefaults данного приложения). А самый простой вариант — попросить пользователя лично ввести свой имейл: по идее, это даже не должно вызвать у него удивления, ведь в iOS действительно существует разновидность окошка, запрашивающего не только пароль, но и имейл.
Я подумал: «Что, если взять этот имеющийся у меня код формы запроса и при помощи подмены SDK проникнуть с ним во много разных приложений, чтобы похищать с помощью них всех пароли от iCloud?» Насколько сложна эта задача?

Предположим, у нас есть совершенно чистый Mac, без каких-либо установленных на него VPN или прокси, но в той же сети есть наш Raspberry Pi. На Mac в Xcode у нас открыт проект iOS-приложения, содержащего абсолютный минимум кода — простое отображение карты местности и не более того.
Теперь открываем браузер, заходим на Amazon Web Services. Находим страничку AWS Mobile SDK и переходим по ссылке на скачивание. Распаковываем скачанный бинарник и перетаскиваем все фреймворки внутрь нашего проекта в Xcode. Затем импортируем библиотеки. От нас даже не требуется вызывать какой-то код — достаточно того, что он будет загружен. Замечу, что в ходе всего процесса Xcode не выдал никаких предупреждений.
Что же происходит при перекомпиляции приложения? На экране появляется та самая копия окошка, предлагающая мне авторизоваться в iTunes Store. Я ввожу пароль, окошко исчезает. Параллельно наблюдая за логом приложения, я вижу как в нём моментально отображается введённый мной пароль — перехват данных Apple ID выполнен. Несложно было бы отправить эти данные куда-то на сервер.
Тут вы можете сказать «Ну, я при разработке сразу заметил бы эту форму ввода и понял, что что-то не так». Но если у тебя аудитория в миллионы пользователей, можно сделать так, чтобы она вылезала только один раз на тысячу, и при тестировании осталась незамеченной.
И опять-таки, много ли нам понадобилось, чтобы осуществить атаку? Нужно было, чтобы в сети находился наш компьютер (и Raspberry Pi оказалось достаточно). HTTP или HTTPs — в данном случае не имело значения, шифрование бы не спасло ситуацию. Все использованные мной программные средства — самые простые, были взяты мной из публичного доступа. При этом я — обыкновенный разработчик, без особого знания и опыта взломов.
Перехват управления
Предыдущий пример внедрял зловредный код в iOS-приложение. А что, если бы нам удалось заполучить контроль над компьютером разработчика вообще? Возможность запустить код на вашем устройстве, как вы понимаете, даёт хакеру огромную власть. Он сможет активировать удалённый доступ через SSH, установить кейлоггер и т.д. Он сможет в любое время наблюдать за вами, фиксировать ваши действия, пользоваться файловой системой. Также он сможет устанавливать новые сертификаты SSL и с помощью них перехватывать все запросы, производимые вами в сеть. Словом, у кого-то есть возможность запустить код на вашем компьютере — и вы полностью скомпрометированы.
Я подумал: «что из iOS SDK я могу использовать для этого?» Есть сервис, предоставляющий SDK посредством команды curl и ссылки HTTP с перенаправлением вывода команде sh. То есть ваш терминал скачает и запустит shell-скрипт.

Сам по себе такой способ установки уже подвергает вас риску, не делайте так. Но в данном случае ещё и использовался протокол HTTP. Что в таком случае возможно сделать?

Предположим, вы — пользователь. Вы заходите на официальную страницу документации. Обращаете внимание на то, что страница шифрована протоколом HTTPS — здорово! Вы копируете команду, запускаете её у себя. Команда выполняется в течение нескольких секунд. Что же успело произойти за это время?
А за это время успел сработать несложный механизм атаки с участием моего Raspberry Pi. Загруженный пользователем shell-скрипт «UpdateSDK» содержал небольшое вкрапление моего собственного кода. И теперь мне разрешён удалённый доступ к вашему компьютеру через SSH, а также у вас был установлен кейлоггер.

Слева вы видите «ваш» терминал, а справа — мой Raspberry Pi, который в режиме реального времени уже показывает всё, что вы вводите на клавиатуре. Запустив с Raspberry Pi SSH, я авторизуюсь, используя логин и пароль, только что прописанные при помощи кейлоггера, и таким образом получаю полный доступ к управлению вашим Mac и к его файловой системе. А также, вероятно, доступ ко многому у вашей компании-работодателя.
В заключение
Насколько вероятно, что такое может произойти с вами? Ведь разработчики всё же стараются использовать безопасный Wi-Fi, покупают себе VPN.
Лично я тоже думал, что осторожен, пока однажды не открыл настройки своего Mac и не обнаружил в истории более 200 подключений к небезопасным сетям Wi-Fi. Каждое такое подключение — это потенциальная угроза. И даже пользуясь проверенной сетью, вы не можете быть на 100% уверенными в своей безопасности, так как не можете знать, не было ли скомпрометировано какое-нибудь из устройств этой сети (как мы только что увидели).
Атаки через небезопасные сети Wi-Fi происходят очень часто. Их легко проводить в публичных местах, таких как гостиницы, кафе, аэропорты и, кстати, конференции 🙂 Представьте, спикер рассказывает о каком-нибудь SDK, и, как водится, параллельно часть зрителей пробует его установить, подключившись к раздаваемому здесь Wi-Fi. А как я уже говорил, злоупотребить своими правами сетевому провайдеру очень легко.
Точно так же и с VPN — вы просто передаёте себя в руки провайдера. И кому лучше довериться — VPN-провайдеру или своей локальной сети и её пользователям? Неясно.
В ноябре 2017 года я провёл своё исследование и проанализировал на наличия в них перечисленных уязвимостей топ 41 самых популярных SDK для iOS (не считая SDK от Google и Facebook, они все надёжно защищены).

Как видите, 31.7% SDK не прошли тестов. Об имеющихся проблемах я сумел сообщить почти всем поставщикам. От одного я получил ответ буквально сразу же, проблема была решена в течение трёх дней. Пятеро команд тоже отреагировали, но потратили на доработку чуть больше времени — около месяца. Семеро же не потрудились ответить на мой репорт вообще и так и не исправили ничего по сей день. Напомню, речь не о каких-то малоизвестных проектах. Все они входят в число самых известных SDK и имеют десятки тысяч пользователей, разрабатывающих с помощью этих SDK iOS-приложения, которые, в свою очередь, используют миллионы пользователей iPhone.
Важно понимать, что пользователи закрытых приложений всегда подвержены большим рискам, пользовали опенсорсных — меньшим. Вы не можете проверить, как работает закрытое приложение. Крайне сложно судить о наличии в нём безопасных решений. Вы можете сравнивать хеши и хеш-суммы, но этим вы, максимум, сумеете проверить успешность загрузки. Опенсорсные продукты, напротив, вы можете исследовать основательно, вдоль и поперёк, а значит, сможете обеспечить себе больше защиты.
Помимо атак man-in-the-middle, существуют и другие. Хакер может атаковать сервер, с которого осуществляется загрузка SDK. Бывает также, что компания, поставляющая SDK, намеренно включает в код так называемые бэкдоры, через которые она впоследствии сможет осуществлять несанкционированный доступ к устройствам пользователей (возможно, местное правительство требует устанавливать бэкдоры, а возможно, это инициатива самой компании).
А мы несём ответственность за продукт, который поставляем. Мы должны быть уверены, что не подводим пользователя и соблюдаем GDPR. Атаки через SDK серьёзны в первую очередь потому, что они массовы — направлены не на одного разработчика, а на миллион пользовательских устройств за раз. Эти атаки могут проходить почти незаметно для вас. Открытый исходный код помогает вам защититься от такого, с закрытым всё гораздо сложнее — используйте его только когда можете смело ему доверять. Спасибо за внимание.
Если вам понравился этот доклад, обратите внимание: следующий петербургский Mobius состоится 22-23 мая, билеты уже в продаже, и постепенно они дорожают.
Как работает проверка доступности API в Swift
Мы постоянно применяем проверки на доступность API, чтобы обеспечить откаты ПО для пользователей, использующих старые версии iOS. А задавались ли вы вопросом, как эту процедуру обрабатывает компилятор Swift? В этой статье мы углубленно изучим внутреннее функционирование условия #availability , выясним, откуда компилятор узнает, доступен ли определенный символ для использования, и как выглядит написанный код после оптимизации.
Я недавно написал предложение по улучшению, в котором предложил добавить в Swift новый атрибут #unavailable . И хотя для его реализации мне не потребовалось выполнять много существенной работы в системе доступности Swift, это дало мне возможность получше разобраться в ее внутреннем устройстве.
Почему проверки #available необходимы?
Несмотря на то, что причина необходимости проверок на доступность API может быть очевидна, предлагаю рассмотреть этот вопрос в чисто образовательных целях и уже потом переходить к изучению внутренних процессов.
Каждый используемый вами код UIKit или Foundation поступает из iOS SDK вашей машины. И хотя пользователи последних версий iOS смогут продолжать использовать ваше приложение даже без обновления для поддержки этих версий, то сами вы сможете применять их новые возможности, только если отправите версию, которая связывается с соответствующим SDK. На данный момент эти SDK поставляются с Xcode, поэтому вы можете быть уверены, что в новой версии Xcode будет присутствовать новая версия iOS. О содержащихся в Xcode SDK всегда можно узнать из описания его версии.
Xcode 12 includes Swift 5.3 and SDKs for iOS 14, iPadOS 14, tvOS 14, watchOS 7 and maccOS Catalina.
Однако несмотря на то, что теперь ваше приложение связывается с верным SDK и использует его возможности, вам не известно, установлена ли у пользователей этого приложения последняя версия iOS. Если бы у вас была возможность поставлять приложение без проверок совместимости, то при задействовании в нем более современных возможностей iOS оно давало бы сбой в случае использования в старых версиях ОС, так как SDK на устройствах их пользователей не содержал бы нужных символов. Поэтому если вы явно не установите в качестве минимальной целевой системы развертывания последнюю доступную версию iOS, то должны использовать условие #available , которое позволит обеспечить подходящий откат для устройств с более старыми версиями.
if #available(iOS 14.0, *) SomeiOS14NewType()
> else SomeOlderType()
>
Здесь (iOS 14.0, *) означает “если это устройство iOS, вернуть true , только если оно содержит iOS 14 SDK. Всегда возвращать true , если это другая платформа ( * )”.
Вы можете использовать только те платформы, которые жестко закодированы в Swift (iOS, OSX, tvOS и watchOS), но при этом в выборе их версии вы не ограничены. Для препятствия же выполнению того или иного кода в компиляторе Swift используются абсурдные номера версий:
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) expectTrue(isP(CFBitVector.makeImmutable(from: [10, 20])))
expectTrue(isP(CFMutableBitVector.makeMutable(from: [10, 20])))
>
Также можно заблокировать типы, чтобы они заработали только в очень отдаленном будущем, хотя это вряд ли будет полезным, если только вы не предскажите, какие в дальнейшем появятся возможности.
@available(iOS, introduced: 999)
final class HologramCreator <>HologramCreator()
// 'HologramCreator' доступен только в iOS 999 или новее
Как работает определение доступности
В компиляторе доступность символов оценивается в фазе проверки типов.
На всякий случай напомню, что фаза проверки типов подразумевает проверку компилятором написанного вами кода на его семантическую верность. На этом этапе у компилятора есть базовое абстрактное синтаксическое дерево вашего кода, которое корректно по своей структуре, но при этом компилятор должен убедиться в возможности выполнения прописанных вами действий. Например, проверить, содержит ли на самом деле тип, на который вы ссылаетесь, вызываемый вами метод? Верны ли возвращаемые типы? И т.д.
Контексты уточнения типов для узлов AST
Конечно, проверка доступности вызываемого вами типа тоже входит в эту фазу. Для выполнения этой проверки компилятор создает контексты уточнения типов (Type Refinement Contexts), являющиеся особыми структурами, способными содержать любую подходящую дополнительную информацию, которая должна присутствовать в области. На данный момент это используется только для интересующих нас символов.
Данный процесс начинается, когда компилятор хочет выполнить проверку типов инструкции, содержащей проверку доступности. Давайте рассмотрим пример:
if #available(iOS 14.0, *) > else >
В этой фазе цель компилятора — найти любые условия доступности и при необходимости создать подходящий уточняющий контекст. Из каждого условия компилятор извлекает данные о собираемой в данный момент платформе и пробует создать диапазон допустимых номеров версий. В этом случае диапазоном будет просто minimumTarget. iOS 14 , при этом ветка else будет хранить свой родительский уточняющий контекст, если только ваше условие не будет проверять на контекст меньшего уровня, чем текущий (что будет наоборот понижать его).
Основной уточняющий контекст позволяет свободно указывать все что угодно, вплоть до минимальной целевой системы развертывания приложения, в связи с чем вам не нужно озадачиваться этими проверками, если вы устанавливаете достаточно высокую версию системы развертки.
if #available(iOS 14.0, *) // Доступность символа: minimumTarget. iOS 14
> else // Доступность символа: 0. minimumTarget (The default)
>
Тот факт, что прорабатываются именно диапазоны, позволяет обнаружить потенциально бесполезные проверки. Если диапазон условия полностью содержится в текущем контексте, то компилятор проигнорирует его и выведет предупреждение:
if #available(iOS 14.0, *), #available(iOS 13.0, *) // (iOS 13.0) Необязательная проверка на 'iOS'; охватывающая область гарантирует, что guard всегда будет true
// Доступность символа: minimumTarget. iOS 14
> else // Доступность символа: 0. minimumTarget (The default)
>
Когда уточняющий контекст для каждой области определен, компилятор будет ассоциировать его с текущим узлом AST инструкции и использовать его для будущих проверок доступности. Уточняющие контексты выстраиваются в виде деревьев (где контекст содержит указатель на своего родителя), но используются при этом как стек. По мере обхода компилятором кода, эти уточняющие контексты при необходимости будут добавляться (push) или извлекаться (pop):
if #available(iOS 9.0, *) // Доступность символа: minimumTarget. iOS 9
if #available(iOS 13.0, *) // Доступность символа: minimumTarget. iOS 13
> else // Доступность символа: minimumTarget. iOS 9
>
> else // Доступность символа: 0. minimumTarget (The default)
>
В то время как внешняя область else вносить изменений в доступность не будет, внутренняя область else будет хранить повышенную доступность iOS 9, поскольку таков на тот момент был уточняющий контекст. Вот наглядный пример того, как это работает на практике:
// Стек уточняющего контекста: [MinimumTarget]
if #available(iOS 9.0, *) // Push: iOS 9 ([MinimumTarget, iOS 9])
if #available(iOS 13.0, *) // Push: iOS 13 ([MinimumTarget, iOS 9, iOS 13])
> else // Pop: iOS 13 ([MinimumTarget, iOS 9])
>
> else // Pop: iOS 9 ([MinimumTarget])
>
Все показанное здесь также применимо к инструкциям guard , только в этом случае положительные изменения доступности применяются к тому, что осталось от текущей области.
guard #available(iOS 14, *) else // Доступность символа: 0. minimumTarget (The default)
return
>
// Доступность символа: minimumTarget. iOS 14
Этот задействующий уточняющие контексты процесс как раз и не дает вам использовать условия доступности вне подобных инструкций:
let isAvailable: Bool = #available(iOS 13.0, *)
if isAvailable // .
>
// Ошибка: #available можно использовать только в качестве условия инструкций 'if', 'guard' или 'while'
И хоть выполнение подобного, на первый взгляд, может иметь смысл, какой тогда должна быть доступность символа этой инструкции if ? Такую процедуру оказалось бы очень тяжело обрабатывать, поскольку теперь каждое создаваемое логическое значение должно также иметь свой уточняющий контекст, что приведет ко множеству ситуаций, в которых компилятор не сможет обработать то, что человек представил себе как возможное.
Определение доступности символа
Создав контекст уточнения типов, компилятор может проверить доступность чего-либо сопоставлением текущего статуса доступности проверяемого элемента с верхним контекстом стека уточнений. Доступность рассматриваемого объявления определяется наличием в его типе атрибута @availability . Если таковой отсутствует, тип будет доступен всегда:
Optional AnnotatedRange = annotatedAvailableRange(D, Ctx);
if (AnnotatedRange.hasValue()) return AnnotatedRange.getValue();
>
// Рассматривать неаннотированные объявления как всегда доступные.
return AvailabilityContext::alwaysAvailable();
Чтобы проверить, доступно или нет конкретное объявление, компилятор извлекает его текущий уточняющий контекст и проверяет, содержится ли он в собственном диапазоне доступности этого объявления. Именно здесь в процесс вступает минимальная целевая система развертки приложения: если уточняющий контекст отсутствует (так как условия доступности еще не рассматривались), то компилятор создаст такой, в котором минимальной целевой версией будет значиться последняя возможная.
И наконец, если эта проверка доступности возвращает false , компилятор выдаст ошибку и предложит исправление, включающее добавление условия доступности:
// Код несколько изменен для лучшей читаемости
bool TypeChecker::isDeclAvailable(const Decl *D,
const DeclContext *referenceDC) ASTContext &Context = referenceDC->getASTContext();
AvailabilityContext declAvailability AvailabilityInference::availableRange(D, Context)>;
AvailabilityContext currentAvailability =
overApproximateAvailabilityAtLocation(referenceDC);
return currentAvailability.isContainedIn(declAvailability);
>
Кроме того, если вы создаете что-либо вне Xcode, то минимальной целевой платформой будет текущая версия того, в чем вы работаете. Например, при выполнении скриптов .swift минимальной целью будет версия вашей macOS:
/// Возвращает минимальную версию платформы, в которой будет развернут код.
///
/// Реализуется только на определенных ОС. Если цель не была
/// настроена, возвращает v0.0.0.
llvm::VersionTuple getMinPlatformVersion() const unsigned major = 0, minor = 0, revision = 0;
if (Target.isMacOSX()) Target.getMacOSXVersion(major, minor, revision);
> else if (Target.isiOS()) Target.getiOSVersion(major, minor, revision);
> else if (Target.isWatchOS()) Target.getOSVersion(major, minor, revision);
>
return llvm::VersionTuple(major, minor, revision);
>
Перевод #available в логическое значение
И наконец, после определения структурной и семантической верности кода компилятор завершает процесс, замещая условия доступности логическими значениями. На данный момент это осуществляется заменой инструкции на вызов _stdlib_isOSVersionAtLeast , которая получает диапазон версий, вычисленный и сохраненный в каждом уточняющем контексте, и возвращает логическое значение, если текущее устройство использует нужную версию:
// До
if #available(iOS 14.0, *) >
// После
if _stdlib_isOSVersionAtLeast(14, 0, 0)
>
Очевидно, что _stdlib_isOSVersionAtLeast работает путем определения текущей версии ОС и проверки ее соответствия переданному значению. Вот как компилятор пробует определить текущую версию ОС:
static os_system_version_s getOSVersion() auto lookup =
(int(*)(struct os_system_version_s * _Nonnull))
dlsym(RTLD_DEFAULT, "os_system_version_get_current_version");
struct os_system_version_s vers = < 0, 0, 0 >;
lookup(&vers);
return vers;
>
Если вам интересно увидеть, как это происходит на практике, то можете попросить компилятор отправить промежуточный язык Swift для конкретного кода таким образом:
swiftc -emit-sil myFile.swift
Выполнив это, вы увидите, что все условия доступности заменены на низкоуровневую проверку версии ОС.
// function_ref _stdlib_isOSVersionAtLeast(_:_:_:)
%5 = function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF
- Понимание врапперов в Swift
- Полезные глобальные функции языка Swift
- Как проще всего выполнять запросы GraphQL в iOS