Apple, боль и сертификаты

Знакомьтесь, Боб — матёрый ios разработчик, Алиса — не менее матёрая тестировщица. Дело было вечером дело было в пятницу. Боб дофиксил багу, вроде бы протестил на своих девайсах. Затем Боб запускает уже отточенные до автоматизма команды:

git checkout develop
git merge bug_fix_#999
git checkout master && git merge develop --no-ff ....
git push ....

На пуш на сервере срабатывает jenkins/teamcity/travis, который запускает билд. В это же самое время наш Боб пишет Алисе, что скоро пойдет домой, и хочет, чтобы аппа ушла сегодня в стор на апрув, дабы выйграть лишние пару дней, так как на носу выходные, если конечно приложение пройдет ручное тестирование Алисы.

Приложение Боба довольное обычное: пару сотен компилируемых класс файлов, еще с десяток cocoapods зависимостей ну и кучка сторибордов — Боб ценит своё и коллег время и не пишет UI в коде, будь как Боб. Боб знает, что его приложение с чистого старта на сервере собирается за 4 минуты для develop версии, которое идет на тест Алисе, и столько же или чуть больше для production версии. Боб также знает, что ему нужно около 10 минут, чтобы дождаться окончания полной сборки и затем сообщить Алисе, что она может приступать к тестированию. Боб человек ответственный, поэтому по истечении 10 минут после пуша проверяет статус билда, так как знает, что сервер это отдельный параллельный мир со своими правилами, законами и странностями.

Пятница, вечер, Боба отделяет от долгожданных выходных только 10 минут, после которых передаст эстафету Алисе. Боб вбивает с сафари bobcompany.ci/dashboard, где видит красную лампочку напротив своего приложения, глаза Боба потускнели, разочарованию не было предела. Боб жмет на show more, где его встречает ошибка:

Code Sign error: No codesigning identities found: No codesigning identities (i.e. certificate and private key pairs) that match the provisioning profile specified in your build settings ("com.company.bob”) were found.

Тут нервы Боба совсем сдают:

9e51e31077294c2bb0de3052b6a269ea.gif

*Кратко об ошибке, она проявляется когда мы пытаемся подписать приложение несуществующим сертификатом, под несуществующим понимается или он не установлен на машине, или он устарел и mobileprovision заведен на более свежую версию сертификата того же аккаунта для того же бандла.

И самое обидное, что эта ошибка только для production версии, которая запускается второй, причем, сначала xcode компилирует зависимости (cocoapods) и уже только после проверяет валидность подписи, когда собирает основное приложение. Поэтому ошибка проявляется примерно во второй половине процесса сборки, из-за чего первые 6–7 минут потрачены в пустую, от чего Боб расстроен еще больше.

Наш разработчик Боб не первый раз сталкивается с этой проблемой, ведь он работает в большой компании, где несколько команд ios девелоперов, где нормальная практика для разработки использовать один Apple аккаунт на несколько человек. Да, Боб в курсе тулзы https://github.com/neonichu/FixCode, но ведь не заставлять же её насильно всем ставить, разработчики нежные создания, не все любят когда их заставляют что-то делать против их воли, Боб сам такой.

Бобу всё это настолько надоело, что он уже забыл, что собирался домой 10 минут назад, вместо этого Боб заказывает пиццу, расчехляет макбук, который уже успел упаковать, наливает кофе, просит бэкендщиков/админов дать доступ до сервера, коннектится туда по ssh и начинает выяснять в чем же именно проблема и как ее можно починить.

Ну окей. Первым делом Боб проверяет какие сертификаты вообще есть на машине:

security find-identity -v login.keychain

что выдает

  1) 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C "iPhone Developer: Mike ... (KSDA3C3QF2)"
  2) 0279CB81AEAD8CE015282DD1FA76CE520A815C4D "iPhone Developer: Bob .. (4WT74HLM2M)"
  3) 79A2544B1A63C3F9D3DA3FFAB199FEAADB7EC306 "iPhone Developer: Alica ... (VJ53F2J4EK)"
  ....
     24 valid identities found

Так, как минимум, в keychain, который используется по дэфолту на сервере, есть 24 сертификата. Боб знает, что каждый mobileprovision файл создается на какой-то один конкретный сертификат. Нужно выяснить на какой сертификат создан mobileprovision файл для которого упал билд и понять, что же произошло с сертификатом. Нужно вообще понять как mobileprovision файл связан с сертификатом. Боб знает, что девелоп версия приложения собралась, поэтому на сервере точно есть это сертификат и сейчас нужно понять как он соотноситься с mobileprovision файлом. Для этого Бобу нужно найти mobileprovision файл и данные о сертификате, чтобы начать искать какие-то соответствия между ними.

Ищем mobileprovision файл по его бандлу ид (если ваш бандл wildcard, то можно искать по любым другим признакам):

cd ~/Library/MobileDevice/Provisioning Profiles
find . -name "*.mobileprovision" -type f -exec grep -H -n -a {} -e "com\.company\.bob" \;

Отлично, мы нашли наш файл:

./f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision:30:              4HUHB9J47M.com.company.bob

В файле есть префикс 4HUHB9J47M у бандла. Из ранее полученного списка сертификатов ничего с этим значением не совпадает. Поэтому Бобу приходится идти в developer.apple.com и искать на какой же аккаунт создан этот mobileprovision файл. Методом тыка он выясняет, что это сертификат Майка:

2) 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C "iPhone Developer: Mike .. (KSDA3C3QF2)"

Отлично, будем работать с этим сертификатом. Боб у нас не криптоаналитик или секурити гай, но он хороший гуглер, поэтому он с легкостью нашел как расковырять сертификат.

Вытаскиваем данные в .pem файл:

security find-certificate -p -c "iPhone Developer: Mike .. (KSDA3C3QF2)" > cert.pem

в файле получим следующее:

-----BEGIN CERTIFICATE-----
MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
...
....
.....
lD+ocFo6+mab/Ph6mTJOZkZu+hnqhzbTD9Q9dXKWkeXAwTqaESNfnhnuOdfCX3vu
YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
z34=
-----END CERTIFICATE-----

Как оказалось с этого файла можно считать информацию о сертификате, попробуем получить наиболее интресную для нас:

openssl x509 -noout -fingerprint -in cert.pem

выдает нам:

SHA1 Fingerprint=40:94:8A:3C:A3:52:7F:58:0B:9E:CB:21:31:DE:6B:19:38:FB:3D:7C

если убрать двоеточия, то получим 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C, это как раз sha1 сертификата Майка, который мы можем увидеть в UI в Keychain:

191b19b140d2430199ad5b2453f6ac7e.png

мы также можем получить срок валидности сертификата, если нужно написать валидатор истекших сертификатов:

openssl x509 -noout -startdate -in cert.pem // Feb 27 07:13:41 2016 GMT
openssl x509 -noout -enddate -in cert.pem // Feb 26 07:13:41 2017 GMT

это же мы видим и в keychain:

69466fcceb254db89482811ab6c9ad5a.png

В общем с сертификатом всё ясно. «Как же его привязать к нашему mobileprovision файлу?» — думает любопытный Боб. Давайте сначала посмотрим на mobileprovision файл поближе:

f1b832aee72f4a4a8b44ab460c4b76f1.jpg

security cms -D -i f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision 

вывод нам дает интересную информацию для поля data, а именно:


MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1
...
...
YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
z34=


Где-то это Боб уже видел, похоже на содержанием ранее полученного .pem файла:

-----BEGIN CERTIFICATE-----
MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
...
....
.....
lD+ocFo6+mab/Ph6mTJOZkZu+hnqhzbTD9Q9dXKWkeXAwTqaESNfnhnuOdfCX3vu
YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
z34=
-----END CERTIFICATE-----

019b5b90d0d54b61ac680581887470cf.gif

Тут Боб понял, вот она зацепка mobileprovision файла на сертификат.

Итого нам нужно взять наш mobileprovision файл, вытащить из него поле data, затем пробежать по всем валидным сертификатам и сравнить с данными из их .pem представления. Боб тут же накидал небольшой скрипт, который передал бэкендщикам, чтобы они его запускали перед каждой сборкой ios проектов. Который работает достаточно просто:

ruby cert_checker.rb f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision

ruby скрипт
require 'active_support/core_ext/hash'

return if ARGV.empty?

xmlString = `security cms -D -i #{ARGV.first}`
data = Hash.from_xml(xmlString)

# получили значение data из mobileprovision
provision_cert_data = data['plist']['dict']['array'].map { |e| e['data'] }.compact.first

# пробегаем по всем сертификатам и собираем их данные в .pem формате
certs = `security find-identity -v login.keychain | grep -o "\\".*\\""`.split("\n").map { |e| e[1..-2] }
pems = certs.map { |e| `security find-certificate -p -c "#{e}"`.split("\n")[1..-2].join('') }
dict = Hash[pems.zip(certs)] 

# сравниваем mobileprovision data с .pem данными сертификатов, если совпадет, значит нашли сертификат для mobileprovision, 
# если не нашли, значит на машине не установлен сертификат

if dict.keys.keep_if { |e| e == provision_cert_data }.empty? 
  puts "Have no certificate for file #{ARGV.first}"
else
  puts "Your mobileprovision issued for #{dict[provision_cert_data]}"
end


Теперь разработчики могут быстро получить фитбэк от сервера, если с сертификатами что-то не так и вообще в пустую не запускать сборку до решения проблемы.

© Habrahabr.ru