Перевод https://www.detroitlabs.com/blog/2017/05/24/securely-signing-jenkins-android-builds/



Безопасная подпись Android сборок в Jenkins CI (Continuous Integration, далее просто CI) это общая проблема. Мы попробовали несколько вариантов за всё время разработки и каждый из них выглядел немного грязновато… кроме одного.


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


Итак, наши варианты:


Включить подписывающий сертификат для сборки, непосредственно в сам репозиторий: Плохо!


Только если вы не используете закрытый репозиторий, вы на самом деле открываете свой сертификат всему миру. Даже если вы используете закрытый репозиторий, вы размещаете сертификат на машине каждого разработчика, что сильно увеличивает шансы что сертификат утечёт.


Загрузить подписывающий сертификат в файловую систему Jenkins и ссылаться на него из Gradle: Неплохо...


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


Использование плагина для подписи Android приложений(Android Signing Plugin): Отлично!


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


Как настроить и использовать Jenkins Android Signing Plugin:


Первое, что вам нужно это убедиться что в вашем Jenkins установлен Credentials Plugin.
Это легко можно проверить взглянув на левую панель в вашем Jenkins. Если вы видите "Credentials" секцию, как показано ниже, то он установлен.
Если у вас нет секции "Credentials", вам нужно не забыть установить Credentials Plugin, это можно сделать одновременно с установкой Android Signing Plugin.


Установка плагина происходит в несколько не сложных шагов:


  1. Выбирете “Manage Jenkins” секцию как показано ниже.


  2. Выберете "Manage Plugins."


  3. Вы должны будете увидеть доступные обновления для текущих плагинов. В верху экрана выберете вкладку "Available".


  4. Теперь вы можете использовать поиск в правом верхнем углу для того, чтобы найти "Credentials Plugin" (Если он не установлен) и "Android Signing Plugin". Поставьте галочки на левой стороне для каждого плагина, затем нажмите "Download now and install after restart" внизу экрана.

Отлично!
Теперь, когда всё установлено, вы можете добавить как минимум один сертификат для подписи приложений. Детальные инструкции по использованию "Credentials Plugin" не являются темой этой статьи, но могут быть легко найдены в интернете. Вам нужно добавить один новый сертификат, как показано ниже.


Как видно, плагин поддерживает только PKCS12 сертификаты. К сожалению, последняя версия Android Studio выдаёт JKS сертификаты, которые оказываются несовместимы с плагином.
Хорошо, что мы имеем удобную утилиту для командной строки "keytool", которая может превратить наш "JKS" в "PKCS12".


keytool -importkeystore -srckeystore {REPLACE_WITH_JKS_FILE} -srcstoretype JKS -deststoretype PKCS12 -destkeystore ConvertedCertificate.p12

Как только у вас будет "PKCS12" файл, вы можете загрузить его. Убедитесь, что ввели пароль, до того как загрузили сертификат, иначе Jenkins не сможет загрузить файл. Теперь вы готовы использовать этот сертификат для любых Android сборок.


Единственно требование к исходному коду, это то что вам нужно оставить "signingConfig" пустым для "buildType", который будет использоваться при сборке Jenkins'ом. Тогда будет создаваться неподписанный APK, который может быть подписан плагином. Имейте в виду, стандартный debug билд подписывается автоматически сгенерированным сертификатом.


Теперь всё готово для подписи приложений, вам нужно добавить шаг "Sign Android APKs" в вашу сборку. Ниже простой пример, сначала мы запускаем Gradle команду для сборки неподписанного релизного билда. После этого, на следующем шаге мы можем подписать билд. Вы должны выбрать сертификат, который вы хотели бы использовать из хранилища сертификатов, указать алиас ключа и путь к неподписанному APK.


Это всё! У вас теперь есть подписанный билд, готовый к распространению сразу же после сборки.


К слову у Google есть своё хранилище сертификатов, которое частично решает проблему с безопасным хранением ключа.

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


  1. akadone
    31.12.2017 02:16
    -2

    Это всё конечно безумно интересно, но есть ли возможность какого-то практического применения этой подписи? Например для защиты приложения от изменения? Только без этих всяких «мы ща обратимся к java… что бы проверить...» это бред. Что-то реальное?


    1. juztoss Автор
      31.12.2017 03:26
      +2

      Она везде практически применяется. И защищает приложение от изменения. Изменить что-то в подписанном файле уже не получится. Соответсвенно не получится, например, добавить какой-то код чтобы следить за пользователем в приложение известной социальной сети и установить его не приложение жертвы. За подленностью сертификата и подписанного им кода будет следить система Android, которая в итоге и не даст изменённое приложение запустить.


      1. akadone
        31.12.2017 16:19

        Это известная рекламная телега от гугла, которая ни как не связана с реалиями. Ведь если бы это было так, то не наводнили 4пда и прочие сайты взломанные версии ПО с гугльплея.
        У меня тот же скайп в телефоне мало того, что отвязан от рекламы, да ещё добавлены ништяки типа кнопки выхода, которых в оригинальной версии нет.
        Понятно, что если писать проверку подписи на интерпретируемых языках, то любой школьник её может убрать за пол часа. Что бы проверить целостность apk нужно писать проверку на c++ иди Delphi. Но всё равно этот код обращается к функциям ОС через java прослойку, которую не сложно изменить, и возвращать требуемое значение, а не реальное. Вот мне и интересно: есть ли способы борьбы с этим — что бы ndk обращалось к ос непосредственно, а не через Dalvik/ART. Что бы хоть кулхацкеров отсеять.


        1. juztoss Автор
          31.12.2017 16:52

          Всё дело в том, что Android в принципе разрешает ставить приложения из сторонних источников. Чтобы это убрать нужно делать как на iOS, но я до конца не знаю как там всё устроено. Во всяком случае как раз таки jailbreak ломает проверку устанавливающихся приложений.


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


    1. punksta
      31.12.2017 17:07
      +1

      В общем случае нет. Вы можете запутывать (obfuscate) код в том числе код проверки подписи приложния. Есть dexguard, например. Еще можете реализовать проверку лицензии developer.android.com/google/play/licensing/overview.html. Последняя выполняется с помощью google play services, в другом процессе. Конечно можно декомпильнуть и вырезать эти проверки, но тогда часть функционала приложения(платные фичи) могут перестать работать. И каждый новый релиз придется взламывать заново. В итоге забивают на часть юзеров, что юзает 4пда.


      1. akadone
        31.12.2017 17:45

        Обфускация — это лишь замена настоящих названий функций/переменных на слабо воспринимаемое человеком. Плюс запутывание кода. Она полезна для защиты от читеров в играх, но обращения к api запутать нельзя.
        Вот хочу я, например, показать баннер. За это отвечает пара классов. Взломщик их просто вычищает, не разбираясь с хитросплетениями запутанного кода. Тоже самое с вызовом проверки лицензии, или проверки подписи. Надо просто вызов функции одного класса заменить на вызов другого. Время работы взломщика увеличится минут на 20, не более.
        Поэтому было бы интересно подобные вещи опустить на уровень ndk, который полезет не к java-прослойке, а сразу к api OS. Специалистов, которые поломают .so на много порядков меньше, чем школьников, которые могут поменять пару байт в интерпретируемом языке.


        1. punksta
          01.01.2018 15:19

          Пишите под ios)


        1. kolipass
          01.01.2018 21:33

          Можно запутать обращение к api:


          • весь трафик пускаем по доверенному каналу;
          • модуль api шифруем и загружаем через кастомный класслоадер;
          • ключ от шифрованного блока можно запрятать с помощью стеганографии

          либо


          • сгенерировать открытый\закрытый ключ, обменяться с сервером по доверенному каналу связи и каждый раз получать новый, зашифрованный открытым ключём блок с модулем api.


        1. MikailBag
          03.01.2018 18:12

          Нет.
          При желании, программу на любом языке можно защитить, например, засунув в виртуалку.
          Единственный вопрос — в скорости (медленности)) работы защиты.


    1. kolipass
      01.01.2018 15:12

      Почему нет, вполне себе существует очень простой вариант проверить текущие сигнатуры (подписи):


      Signature[] signatures = getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
          for (Signature signature : signatures) {
              // checkig
          }

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


  1. amarao
    31.12.2017 15:05
    +2

    У jenkins есть система управления credentials. С андроидными подписями я не сталкивался, а всё остальное, что мы делаем, идёт либо через inject environment variables, либо через ssh-ключи на слейвы, которые могут что-то делать (например, подписывать пакеты).

    Вообще, если говорить про сертификаты, то логично, чтобы jenkins всего лишь делал request, а сертификат ему делал сторонний CA.


  1. ivan2kh
    31.12.2017 17:54

    Мне нравится идея выдавать Jenkins временный токен для отдельного сервера подписывания.