it-swarm.com.ru

Как закрепить открытый ключ сертификата на iOS

Повышая безопасность разрабатываемого нами приложения для iOS, мы обнаружили, что PIN (полностью или частично) SSL-сертификат сервера для предотвращения атак «человек посередине». 

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

Но как ты это делаешь?

47
Javier Quevedo

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

Прежде всего, добавьте структуру безопасности. 

#import <Security/Security.h>

Добавить библиотеки openssl. Вы можете скачать их с https://github.com/st3fan/ios-openssl

#import <openssl/x509.h>

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

Сначала получите сертификат в формате X509 (для этого вам понадобятся библиотеки ssl)

const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);

Теперь мы подготовимся к чтению данных с открытым ключом

ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];    

На этом этапе вы можете перебрать строку pubKey2 и извлечь байты в формате HEX в строку с помощью следующего цикла

 for (int i = 0; i < pubKey2->length; i++)
{
    NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
    publicKeyString = [publicKeyString stringByAppendingString:aString];
}

Распечатайте открытый ключ, чтобы увидеть его

 NSLog(@"%@", publicKeyString);

Полный код

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];    

for (int i = 0; i < pubKey2->length; i++)
 {
     NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
     publicKeyString = [publicKeyString stringByAppendingString:aString];
 }

if ([publicKeyString isEqual:myPinnedPublicKeyString]){
    NSLog(@"YES THEY ARE EQUAL, PROCEED");
    return YES;
}else{
   NSLog(@"Security Breach");
   [connection cancel];
   return NO;
}

}
34
Javier Quevedo

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

Что вам нужно сделать, это:

  1. Используйте NSURLConnectionDelegate для извлечения данных и реализуйте willSendRequestForAuthenticationChallenge.
  2. Включите справочный сертификат в DER формате. В примере я использовал простой файл ресурсов.
  3. Извлечь открытый ключ, представленный сервером
  4. Извлеките открытый ключ из вашего справочного сертификата
  5. Сравните два
  6. Если они совпадают, продолжайте регулярные проверки (имя хоста, подписание сертификата и т.д.)
  7. Если они не совпадают, потерпите неудачу.

Пример кода:

 (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    // get the public key offered by the server
    SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
    SecKeyRef actualKey = SecTrustCopyPublicKey(serverTrust);

    // load the reference certificate
    NSString *certFile = [[NSBundle mainBundle] pathForResource:@"ref-cert" ofType:@"der"];
    NSData* certData = [NSData dataWithContentsOfFile:certFile];
    SecCertificateRef expectedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);

    // extract the expected public key
    SecKeyRef expectedKey = NULL;
    SecCertificateRef certRefs[1] = { expectedCertificate };
    CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *) certRefs, 1, NULL);
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    SecTrustRef expTrust = NULL;
    OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &expTrust);
    if (status == errSecSuccess) {
      expectedKey = SecTrustCopyPublicKey(expTrust);
    }
    CFRelease(expTrust);
    CFRelease(policy);
    CFRelease(certArray);

    // check a match
    if (actualKey != NULL && expectedKey != NULL && [(__bridge id) actualKey isEqual:(__bridge id)expectedKey]) {
      // public keys match, continue with other checks
      [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
    } else {
      // public keys do not match
      [challenge.sender cancelAuthenticationChallenge:challenge];
    }
    if(actualKey) {
      CFRelease(actualKey);
    }
    if(expectedKey) {
      CFRelease(expectedKey);
    }
 }

Отказ от ответственности: это только пример кода, и он не был тщательно протестирован Для полной реализации начните с примера закрепления сертификата OWASP .

И помните, что закрепления сертификатов всегда можно избежать с помощью SSL Kill Switch и подобных инструментов.

19
beetstra

Вы можете выполнить закрепление SSL с открытым ключом, используя функцию SecTrustCopyPublicKey в Security.framework. См. Пример в connection: willSendRequestForAuthenticationChallenge: проекта AFNetworking.

Если вам нужен openSSL для iOS, используйте https://Gist.github.com/foozmeat/5154962 Он основан на st3fan/ios-openssl, который в настоящее время не работает.

9
Jano

Вы можете использовать плагин PhoneGap (Build), упомянутый здесь: http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/ 734

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

5
Eddy Verbruggen

Если вы используете AFNetworking (точнее, AFSecurityPolicy) и выбираете режим AFSSLPinningModePublicKey, не имеет значения, изменяются ваши сертификаты или нет, если открытые ключи остаются прежними. Да, это правда, что AFSecurityPolicy не предоставляет метод для вас непосредственно установить ваши открытые ключи; Вы можете установить свои сертификаты, только позвонив setPinnedCertificates. Однако, если вы посмотрите на реализацию setPinnedCertificates, вы увидите, что платформа извлекает открытые ключи из сертификатов, а затем сравнивает ключи. 

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

Следующий код работает для меня. 

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
3
SeaJelly

Здесь ответ Swifty. Сохраните сертификат (как файл .cer) вашего сайта в основном комплекте. Затем используйте this метод URLSessionDelegate:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    guard
        challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
        let serverTrust = challenge.protectionSpace.serverTrust,
        SecTrustEvaluate(serverTrust, nil) == errSecSuccess,
        let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {

            reject(with: completionHandler)
            return
    }

    let serverCertData = SecCertificateCopyData(serverCert) as Data

    guard
        let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"),
        let localCertData = NSData(contentsOfFile: localCertPath) as Data?,

        localCertData == serverCertData else {

            reject(with: completionHandler)
            return
    }

    accept(with: serverTrust, completionHandler)

}

... 

func reject(with completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.cancelAuthenticationChallenge, nil)
}

func accept(with serverTrust: SecTrust, _ completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.useCredential, URLCredential(trust: serverTrust))
}

Вы можете получить файл .cer с Chrome, как это .

2
schirrmacher

... для закрепления всего сертификата. Такая практика создает проблему ...

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

Я полагаю, что Google делает это для того, чтобы CRL, OCSP и списки отзыва были управляемыми, и я ожидаю, что это сделают и другие. Для моих сайтов я обычно заново сертифицирую ключи, чтобы обеспечить непрерывность ключей.

Но как ты это делаешь?

Сертификат и закрепление открытого ключа . В статье обсуждается практика и предлагается пример кода для OpenSSL, Android, iOS и .Net. Существует по крайней мере одна проблема с iOS, имеющая отношение к платформе, обсуждаемой в iOS: обеспечить значимую ошибку от NSUrlConnection didReceiveAuthenticationChallenge (Ошибка сертификата) .

Кроме того, Питер Гутманн прекрасно рассматривает последовательность и закрепление в своей книге Инженерная безопасность .

1
jww

Если вы используете AFNetworking, используйте AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];

0
Oshitha Wimalasuriya