Блог Roxie Mobile

SSL-пиннинг в iOS на Swift

16 января 2018 15 984
Безопасность

Как только почти все сообщество iOS разработчиков переключилось с Objective-C на Swift, изменились и наши предпочтения в библиотеках и сетях.

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

Два метода пиннинга (фиксирования)

Пока AFNetworking обращается только к серверам, чьи сертификаты вы указали, Alamofire работает иначе – вы привязываете сертификат к домену, в дальнейшем проверка сертификата будет проходить только, когда вы указываете лист заранее выбранных доменов. Все другие домены проверяться не будут. С другой стороны, AFNetworking будет блокировать все обращения, которые не прошли проверку сертификатов, закрепленных в вашем приложении.

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

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

Инструкция по применения SSL пиннига (фиксирования)

Ваше приложение уже почти готово, и вы хотите добавить еще один уровень безопасности с SSL-пининнгом.

Получение сертификата

Если ваше приложение уже рабочее, вы можете легко считать сертификат самостоятельно. Firefox предлагает отличную возможность экспортировать их используя UI:

Альтернативный способ, когда вы используете openssl из командной строки и получаете сертификат таким способом:

 

1
openssl s_client -showcerts -connect www.infinum.co:443 < /dev/null | openssl x509 -outform DER > infinumco.cer

Пиннинг с Swift и Alamofire

Пример политики безопасности для Alamofire и пиннинг (фиксирование) может выглядеть примерно так:

1
2
3
4
5
6
7
8
9
10
11
12
13
let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "infinum.co": .pinPublicKeys(
        publicKeys: ServerTrustPolicy.publicKeys(),
        validateCertificateChain: true,
        validateHost: true
    )
]

let sessionManager = SessionManager( // Make sure you keep a reference of this guy somewhere
    serverTrustPolicyManager: ServerTrustPolicyManager(
        policies: serverTrustPolicies
    )
)

В этом примере мы фиксируем публичные ключи от сертификатов. Вы можете закрепить сами сертификаты (в этом случае вы сопоставляете байт с байтными данными) или сравнить публичные ключи в сертификатах. У публичных ключей есть преимущество – они более надежные. Сертификат сервера может быть обновлен сохраненным публичным ключом, поэтому обновление приложения не требуется для замены сертификатов. Для практики это не типичный кейс, но дальше мы расскажем об этом подробнее.

Чтобы имитировать поведение AFNetworking, к которому мы прибегали ранее, вам придется разделить на подклассы Server Trust Policy Manager и отменить (аннулировать) реализация по умолчанию. Пример, как легко остановить в приложении коммуникацию, если не были зафиксированы сертификаты, может выглядеть примерно так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import UIKit
import Alamofire

class CustomServerTrustPolicyManager: ServerTrustPolicyManager {

    override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
        // Check if we have a policy already defined, otherwise just kill the connection
        if let policy = super.serverTrustPolicy(forHost: host) {
            print(policy)
            return policy
        } else {
            return .customEvaluation({ (_, _) -> Bool in
                return false
            })
        }
    }

}

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

Если в вашем проекте по умолчанию используется сеть, вы можете вообще не пользоваться AFNetworking или Alamofire, а реализовать привязку на уровне NSURLSession. Так будет проще, чем мучиться с Alamofire. Поскольку этот способ более гибкий и не будет ломаться в случае изменений Alamofire API в будущем.

Вы можете установить сертификат:

1
2
3
4
5
6
7
8
9
// Compare the server certificate with our own stored
if let serverCertificate = SecTrustGetCertificateAtIndex(trust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data

    if pinnedCertificates().contains(serverCertificateData) {
        completionHandler(.useCredential, URLCredential(trust: trust))
        return
    }
}

Или публичный ключ:

1
2
3
4
5
6
7
// Or, compare the public keys
if let serverCertificate = SecTrustGetCertificateAtIndex(trust, 0), let serverCertificateKey = publicKey(for: serverCertificate) {
    if pinnedKeys().contains(serverCertificateKey) {
        completionHandler(.useCredential, URLCredential(trust: trust))
        return
    }
}

Наконец, вы можете использовать вместе URLSessionDelegate и Alamofire, поскольку SessionDelegate – это открытый класс. В этом случае вам следует создать подклассы SessionDelegate с вашим собственным классом и использовать код из предыдущего примера в переменной sessionDidReceiveChallengeWithCompletion:

1
2
3
4
5
6
7
8
9
10
11
12
class CustomSessionDelegate: SessionDelegate {

    // Note that this is the almost the same implementation as in the ViewController.swift
    override init() {
        super.init()

        // Alamofire uses a block var here
        sessionDidReceiveChallengeWithCompletion = { session, challenge, completion in
            // See above, or check the code example
        }
    }
}

Смотрите полный пример кода по ссылке

Распространенные ошибки

Тестирование пин

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

Если в приложении связь возможна только с конечными точками, тогда тестирование проходит также же просто как запрос GET к произвольному сайту (только убедитесь в наличии сертификата). Приложение должно отменить соединение, и запрос должен завершиться неудачно. Дополнительно убедитесь, что API работает, как ожидалось, и вы готовы к работе.

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

Изменение сертификата/обновление

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

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

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

Напутствие

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

Оригинал статьи: тут

Запросить консультацию