Как реализовать клиентские сертификаты и аутентификацию сервера для iOS

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

Справочная информация. Продукт/услуга, для которых я пытался обеспечить безопасность, построены на основе служб WCF на сервере Windows, которые доступны только через пользовательские клиентские приложения на ПК или iPad. Один сервер на клиента, без доступа через браузер. Все уже было защищено TLS с аутентификацией и авторизацией с использованием стандартных механизмов Windows и сертификатов коммерческого центра сертификации.

Чтобы еще больше ограничить доступ, клиент-серверные сертификаты были реализованы для платформы Windows с использованием самозаверяющих сертификатов (коммерческие центры сертификации не требуются в случаях, когда существует взаимная аутентификация без общедоступного/браузерного доступа, несмотря на утверждения об обратном, и они сложнее). справляться).

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

Спасибо


person saminpa    schedule 08.03.2016    source источник


Ответы (2)


Основные шаги:

  1. Создайте систему для генерации сертификатов (просто, но нетривиально, если это производственная система)
  2. Перенесите сертификаты на iPad (НЕ в комплекте с магазином приложений!)
  3. Сохраните все полученные учетные данные в цепочке ключей приложения (где, по словам Apple, они принадлежат)
  4. Получите сохраненные учетные данные из цепочки для ключей для использования в NSURLConnections.
  5. Фактически аутентифицируйте сертификат сервера и возвращайте учетные данные клиента

Шаг 1. Создайте сертификаты

Ссылка: http://developer-should-know.tumblr.com/post/127063737582/how-to-create-your-own-pki-with-openssl

Вы можете использовать другие методы, кроме OpenSSL для Windows [http://slproweb.com/products.html]. это довольно круто, за исключением того, что стандартный интерфейс — cmdline, а документацию сложно понять.

То, что я хотел бы, чтобы кто-то объяснил мне заранее, очевидно, но нет: [a] приложение устанавливается в каталог корневого уровня и включает файлы конфигурации, которые используются по умолчанию для настроек, которые не указаны в командной строке [b] расположение промежуточных и выходных файлов должно быть указано в файлах конфигурации [c] определенные файлы должны быть созданы вручную перед запуском команд [d] вы должны создать структуру файлов/папок, которая подходит для того, что вы пытаетесь сделать, а затем соответствующим образом настройте файлы cfg.

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

c:\sslcert
    root
    certs
        YourCompany (duplicate this structure as required)
             intermediate
             server
             client
             crl (optional)

В папке sslcert верхнего уровня

.rnd        (empty file)
certindex.txt   (empty file)
serial.txt  (Text file seeded with the text “01”, hold the quotes)

В корневой папке

RootCA.cfg

В папке сертификаты\шаблон

IntermediateCA.cfg

Установите рабочий каталог и запустите OpenSSL cd \sslcert c:\OpenSSL-Win32\bin\openssl.exe

Создайте корневой ключ и сертификат за один шаг

req -config ./root/RootCA.cfg -new -x509 -days 7300 -extensions v3_ca -keyout root/YourCompanyRootCAkey.pem -out root/YourCompanyRootCAcert.cer

ПРИМЕЧАНИЕ для новичков: -extensions позволяет вам применить один из нескольких подразделов в одном файле cfg.

Проверьте ключ и сертификат (необязательно)

x509 -noout -text -in root/YourCompanyRootCAcert.cer

Запросить новый промежуточный сертификат

req -config certs/YourCompany/IntermediateCA.cfg -new -keyout certs/YourCompany/intermediate/intermediateCAkey.pem -out certs/YourCompany/intermediate/intermediateCAreq.pem  

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

ca -config root/RootCA.cfg -extensions v3_intermediate_ca -days 3650 -notext -in certs/YourCompany/intermediate/intermediateCAreq.pem -out certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer

Проверьте ключ и сертификат (необязательно)

x509 -noout -text -in certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer

Создайте файл цепочки сертификатов, объединив промежуточный и корневой сертификаты (это просто добавление из командной строки — новая цепочка будет добавлена ​​в окончательный пакет p12)

c:\sslcert> type c:\sslcert\certs\YourCompany\intermediate\YourCompanyIntermediateCAcert.cer c:\sslcert\root\YourCompanyRootCAcert.cer > c:\sslcert\certs\YourCompany\intermediate\YourCompanyCAchain.cer

Запрос нового ключа клиента и сертификата

genrsa -aes256 -out certs/YourCompany/client/YourCompanyClientkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key 
certs/YourCompany/client/YourCompanyClientkey.pem -new -sha256 -out         certs/YourCompany/client/YourCompanyClientreq.pem

Подпишите и протестируйте сертификат клиента с промежуточными полномочиями

ca -config certs/YourCompany/IntermediateCA.cfg -extensions usr_cert -days 1095 -notext -md sha256 -in certs/YourCompany/client/YourCompanyClientreq.pem -out certs/YourCompany/client/YourCompanyClientcert.cer
x509 -noout -text -in certs/YourCompany/client/YourCompanyClientcert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/client/YourCompanyClientcert.cer

Упаковать клиентский сертификат

pkcs12 -export -in certs/YourCompany/client/YourCompanyClientcert.cer -name “YourCompany Smips Client” -inkey certs/YourCompany/client/YourCompanyClientkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/client/YourCompanyClientWithName.p12

Переименуйте pkcs для импорта в iOS из электронной почты/iTunes.

c:\sslcert> copy c:\sslcert\certs\YourCompany\client\YourCompanyClient.p12 c:\sslcert\certs\YourCompany\client\YourCompanyClient.yourext12

Запрос нового ключа сервера и сертификата

genrsa -aes256 -out certs/YourCompany/server/YourCompanyServerkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key certs/YourCompany/server/YourCompanyServerkey.pem -new -sha256 -out certs/YourCompany/server/YourCompanyServerreq.pem

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

ca -config certs/YourCompany/IntermediateCA.cfg -extensions server_cert -days 1095 -notext -md sha256 -in certs/YourCompany/server/YourCompanyServerreq.pem -out certs/YourCompany/server/YourCompanyServercert.cer
x509 -noout -text -in certs/YourCompany/server/YourCompanyServercert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/server/YourCompanyServercert.cer

Сертификат сервера пакетов

pkcs12 -export -in certs/YourCompany/server/YourCompanyServercert.cer -name “YourCompany Smips Server” -inkey certs/YourCompany/server/YourCompanyServerkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/server/YourCompanyServer.p12

Вот файлы cfg: Root

dir                 = .

[ ca ]
default_ca              = CA_default

[ CA_default ]
serial              = $dir/serial.txt
database                = $dir/certindex.txt
new_certs_dir           = $dir/certs
certs                   = $dir/certs
private_key             = $dir/root/yourcompanyRootCAkey.pem
certificate             = $dir/root/yourcompanyRootCAcert.cer
default_days            = 7300
default_md              = sha256
preserve                = no
email_in_dn             = no
nameopt             = default_ca 
certopt             = default_ca 
policy              = policy_strict

[ policy_strict ]
countryName                 = match
stateOrProvinceName         = match
organizationName            = match
organizationalUnitName      = optional
commonName                  = supplied
emailAddress                = optional

[ req ]
default_bits            = 4096      # Size of keys
default_keyfile         = key.pem       # name of generated keys
default_md              = sha256        # message digest algorithm
string_mask             = nombstr       # permitted characters
distinguished_name      = req_distinguished_name
x509_extensions         = v3_ca

[ req_distinguished_name ]
0.organizationName           = Organization Name
organizationalUnitName       = Organizational Unit Name
emailAddress                 = Email Address
emailAddress_max            = 40
localityName            = Locality Name (city, district)
stateOrProvinceName     = State or Province Name (full name)
countryName             = Country Name (2 letter code)
countryName_min         = 2
countryName_max         = 2
commonName              = Common Name (hostname, IP, or your name)
commonName_max          = 64

0.organizationName_default  = yourcompany
organizationalUnitName_default  = yourcompanyRoot Certification
emailAddress_default        = [email protected]
localityName_default        = Okeefenokee
stateOrProvinceName_default = Wisconsin
countryName_default     = US

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ crl_ext ]
authorityKeyIdentifier=keyid:always

Средний

dir = .

# [For non-command-line folks, everything is keyed to the working directory here (.) so if your working prompt says c:\sslcerts> then the cfg will look for serial.txt at c:\sslcerts\serial.txt and bomb if it doesn’t find things laid out accordingly. Thats why you set up a directory structure to match these entries]

[ ca ]
default_ca              = CA_default

[ CA_default ]
serial                  = $dir/serial.txt
database                = $dir/certindex.txt
crl_dir                 = $dir/certs/yourcompany/crl
new_certs_dir               = $dir/certs
certs                   = $dir/certs
private_key             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
certificate             = $dir/certs/yourcompany/intermediate/yourcompanyIntermediateCAcert.cer
default_days                = 3650
default_md              = sha256
preserve                = no
email_in_dn             = no
nameopt                 = default_ca
certopt                 = default_ca 
crlnumber               = $dir/certs/yourcompany/crl/crlnumber
crl                 = $dir/certs/yourcompany/crl/crl.pem
crl_extensions              = crl_ext
default_crl_days            = 365
policy                  = policy_loose

[ policy_loose ]
countryName                     = optional
stateOrProvinceName             = optional
localityName                    = optional
organizationName                = optional
organizationalUnitName          = optional
commonName                      = supplied
emailAddress                    = optional

[ req ]
default_bits                = 4096              # Size of keys
default_keyfile             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
default_md              = sha256            # message digest 

# the old default was md1 - change this]

algorithm
string_mask             = nombstr           # permitted characters
distinguished_name          = req_distinguished_name
x509_extensions             = v3_intermediate_ca

[ req_distinguished_name ]
0.organizationName                  = Organization Name
organizationalUnitName              = Organizational Unit Name
emailAddress                        = Email Address
emailAddress_max            = 40
localityName                = Locality Name (city, district)
stateOrProvinceName         = State or Province Name (full name)
countryName             = Country Name (2 letter code)
countryName_min             = 2
countryName_max             = 2
commonName              = Common Name (hostname, IP, or your name)
commonName_max              = 64

0.organizationName_default      = yourcompany
organizationalUnitName_default      = yourcompany Intermediate Certification
emailAddress_default            = [email protected]
localityName_default            = Okeefenokee
stateOrProvinceName_default     = Wisconsin [should be spelled out]
countryName_default         = US

[ v3_intermediate_ca ]
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid:always,issuer
basicConstraints            = critical, CA:true, pathlen:0
keyUsage                = critical, digitalSignature, cRLSign, keyCertSign

# Important - the pathlen parameter prevents this cert from being used to create new intermediate certs. The subsequent subsections for server and client certs allows you to specify their type and intended usage, as distinct from the intermediate cert, in the same cfg file 

[ usr_cert ]
basicConstraints            = CA:FALSE
nsCertType              = client, email
nsComment               = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer
keyUsage                = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage            = clientAuth, emailProtection

[ server_cert ]
basicConstraints            = CA:FALSE
nsCertType              = server
nsComment               = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer:always
keyUsage                = critical, digitalSignature, keyEncipherment
extendedKeyUsage            = serverAuth

[ crl_ext ]
authorityKeyIdentifier          = keyid:always

<сильный>2. Перенос сертификатов на iPad

Ссылка: как зарегистрировать приложение, чтобы открыть файл PDF в моем приложении на iPad

Apple рекомендует зарегистрировать новый тип файла, обрабатываемый вашим приложением, и передать файл p12, переименованный с новым пользовательским расширением, на устройство (вручную или по электронной почте) для установки клиентских сертификатов. Файл p12 должен включать общедоступную цепочку сертификатов, а также информацию о сертификате клиента, как указано в шаге 1 выше. Когда вы пытаетесь открыть такой файл, устройство отправляет запуск/пробуждение вашему делегату приложения, который вам нужно обработать (не в didload, потому что это может быть пробуждение).

Это немного изменилось с v8 или 9, но мне нужно поддерживать 7, так что это для устаревшего обработчика. То же решение, но оно начинается с добавления в файл plist приложения, как показано на снимках экрана ниже.

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

введите описание изображения здесь

введите описание изображения здесь

Далее вам нужен делегат/обработчик, который не требует пояснений. Поскольку эта часть не имеет ничего общего с обычным потоком управления, я обрабатываю всю обработку делегатов в AppDelegate.m. (это так неправильно?) Настройте методы/переменные по мере необходимости и, пожалуйста, игнорируйте параноидальную дополнительную проверку существования файла...

Ссылка: https://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1

- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication
           annotation:(id)annotation {

    if (url) {

        self.p12Data = [NSData dataWithContentsOfFile:[url path]];

        if (!p12Data) {
            [self messageBox:@"Warning" : @"Failed to read data file, cancelling certificate import"];
        }
        else {
            [self presentAlertViewForPassPhrase];
        }

        NSFileManager * fileManager = [NSFileManager defaultManager];
        if ( [fileManager fileExistsAtPath:[url path]] ) {
            [fileManager removeItemAtPath:[url path] error:NULL];
        }
    }

    return YES;
}

- (void)presentAlertViewForPassPhrase {

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Certificate Credentials"
                                                    message:@"Please enter the passphrase for your certificate"
                                                   delegate:self
                                          cancelButtonTitle:@"Cancel"
                                          otherButtonTitles:@"Done", nil];
    [alert setAlertViewStyle:UIAlertViewStyleSecureTextInput];
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {

    if (buttonIndex == 1) {                                             // User selected "Done"
        UITextField *ppField = [alertView textFieldAtIndex:0];
        if ([ppField.text length] > 0) {
            [self loadCertificates:ppField.text];
        }
        //Handle Else
    }
    else
    {                                                                   // User selected "Cancel"
        [self messageBox:@"Information" : @"Certificate import cancelled"];
    }
}

<сильный>3. Сохраните полученные учетные данные в цепочке ключей приложения

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

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

Это ключевые ссылки:

Перечислить все элементы связки ключей в моем приложении для iOS

[важно, чтобы помочь визуализировать, как выглядит ваша цепочка для ключей]

Как удалить все элементы цепочки для ключей, доступные приложению?< /а>

Что делает элемент связки ключей уникальным (в iOS)?

http://help.sap.com/saphelp_smp307sdk/helpdata/en/7c/03830b70061014a937d8267bb3f358/content.htm

[https://developer.apple.com/library/ios/samplecode/AdvancedURLConnections/Listings/Credentials_m.html, в котором говорится:

// IMPORTANT: SecCertificateRef's are not uniqued (that is, you can get two
// different SecCertificateRef values that described the same fundamental
// certificate in the keychain), nor can they be compared with CFEqual. So
// we match up certificates based on their data values.

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

Код (объяснение следует):

- (void) loadCertificates:(NSString *)passPhrase {

    BOOL lastError = false;
    NSMutableDictionary * p12Options = [[NSMutableDictionary alloc] init];
    [p12Options setObject:passPhrase forKey:(id)kSecImportExportPassphrase];
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    OSStatus err = SecPKCS12Import((CFDataRef)p12Data, (CFDictionaryRef)p12Options, &items);
    if (err != noErr) {
        [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
        lastError = true;
    }
    if (!lastError && err == noErr && CFArrayGetCount(items) > 0) {
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
        //Clean-up

        NSArray *secItemClasses = [NSArray arrayWithObjects:
                                   (id)kSecClassCertificate,
                                   (id)kSecClassKey,
                                   (id)kSecClassIdentity,
                                   nil];

        for (id secItemClass in secItemClasses) {
            NSDictionary *spec = @{(id)kSecClass: secItemClass};
            err = SecItemDelete((CFDictionaryRef)spec);
        }

        //Client Identity & Certificate

        SecIdentityRef clientIdentity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);

        NSDictionary *addIdentityQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                          kClientIdentityLabel, kSecAttrLabel,
                                          (id)clientIdentity, kSecValueRef,
                                          nil];
        err = SecItemAdd((CFDictionaryRef)addIdentityQuery, NULL);
        if (err == errSecDuplicateItem) {
            NSLog(@"Duplicate identity");
        }
        if (err != noErr) {
            [self messageBox:@"Warning" : @"Failed to save the new identity"];
            lastError = true;
        }
        //Server Certificate
        CFArrayRef chain = CFDictionaryGetValue(identityDict, kSecImportItemCertChain);
        CFIndex N = CFArrayGetCount(chain);
        BOOL brk = false;
        for (CFIndex i=0; (i < N) && (brk == false); i++) {
            SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(chain, i);
            CFStringRef summary = SecCertificateCopySubjectSummary(cert);
            NSString* strSummary = [[NSString alloc] initWithString:(NSString *)summary];
            if ([strSummary containsString:@"Root"] || (i == N)) {

                NSDictionary *addCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                              kServerCertificateLabel, kSecAttrLabel,
                                              (id)cert, kSecValueRef,
                                              nil];
                err = SecItemAdd((CFDictionaryRef)addCertQuery, NULL);
                if (err == errSecDuplicateItem) {
                    NSLog(@"Duplicate root certificate");
            }
            if (err != noErr) {
                [self messageBox:@"Warning" : @"Failed to save the new server certificate"];
                lastError = true;
            }
            brk = true;
        }
        [strSummary release];
        CFRelease(summary);
    }
}
else {
    [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
    lastError = true;
}
    [p12Options release];
    CFRelease(items);
    if (!lastError) [self messageBox:@"Information" : @"Certificate import succeeded"];
}

где kClientIdentityLabel и kServerCertificateLabel — произвольные метки.

Функции kSec слишком многочисленны/сложны, чтобы подробно объяснять их здесь. Достаточно сказать, что все очищается, затем сохраняется извлеченный идентификатор клиента, после чего следует извлечение корневого ЦС, который затем сохраняется отдельно. Почему петля? потому что я не знал, правильно ли с технической точки зрения предположить, что корень находится в конце цепочки, но это будет, если я сгенерирую p12, так что код пока есть.

Обратите внимание, что ошибки из kSec закодированы, поэтому этот сайт незаменим: https://www.osstatus.com

<сильный>4. Получить сохраненные учетные данные из цепочки для ключей

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

- (void) reloadCredentials {

    self.clientCredential = nil;
    self.serverCertificateData = nil;

    if (self.useClientCertificateIfPresent) {

        NSDictionary* idQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                 kClientIdentityLabel,            kSecAttrLabel,
                                 (id)kSecClassIdentity,           kSecClass,
                                 kCFBooleanTrue,                  kSecReturnRef,
                                 kSecMatchLimitAll,               kSecMatchLimit,
                                 nil];
        CFArrayRef result = nil;
        OSStatus err = SecItemCopyMatching((CFDictionaryRef)idQuery, (CFTypeRef*)&result);
        if (err == errSecItemNotFound) {
            [self messageBox:@"Warning" : @"Client credentials not found. Server connection may fail"];
        }
        else if (err == noErr && result != nil ) {

            SecIdentityRef clientIdentity = (SecIdentityRef)CFArrayGetValueAtIndex(result, 0);

            SecCertificateRef clientCertificate;
            SecIdentityCopyCertificate(clientIdentity, &clientCertificate);
            const void *certs[] = { clientCertificate };
            CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
            self.clientCredential = [NSURLCredential credentialWithIdentity:clientIdentity certificates:(NSArray*)certsArray
                                                                      persistence:NSURLCredentialPersistenceNone];
            CFRelease(certsArray);
            CFRelease(clientCertificate);
            CFRelease(result);
        }
        else {
            [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
        }

        NSDictionary* serverCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                         kServerCertificateLabel,         kSecAttrLabel,
                                         (id)kSecClassCertificate,        kSecClass,
                                         kCFBooleanTrue,                  kSecReturnRef,
                                         kSecMatchLimitAll,               kSecMatchLimit,
                                         nil];
        CFArrayRef result1 = nil;
        err = SecItemCopyMatching((CFDictionaryRef)serverCertQuery, (CFTypeRef*)&result1);
        if (err == errSecItemNotFound) {
            [self messageBox:@"Warning" : @"Server certificate not found. Server connection may fail"];
        }
        else if (err == noErr && result1 != nil ) {

            SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(result1, 0);
            CFDataRef certRefData = SecCertificateCopyData(certRef);
            self.serverCertificateData = (NSData *)certRefData;
            CFRelease(certRefData);
            CFRelease(result1);
        }
        else {
            [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
        }
    }
}

<сильный>5. Подтвердить подлинность сертификата сервера и вернуть учетные данные клиента

Ху мальчик. Это редактирование, объясняющее, как на самом деле использовать извлеченные сертификаты (предполагалось, что это будет очевидная часть...)

Во-первых, и без того сомнительная документация Apple устарела благодаря новой структуре Application Transport Security (см., например: http://useyourloaf.com/blog/app-transport-security/). Я не собираюсь вдаваться в подробности, но идея состоит в том, чтобы заставить всех всегда использовать https и доверенные сертификаты по умолчанию. Для моего сценария с закреплением сертификата и взаимной аутентификацией между выделенными клиентами и частным сервером вы можете безопасно отключить эту функцию, добавив словарь в свой plist следующим образом:

введите описание изображения здесь

Затем, на шаге 4, у вас уже были учетные данные клиента для немедленного ответа на этот вызов, когда он попадает, но сертификат сервера плавает как NSData в формате DER, созданный SecCertificateCopyData, и неясно, что должно произойти, когда этот вызов прибудет.

Оказывается, вам нужно реализовать алгоритм из раздела 6 «Стандарта X.509» (https://tools.ietf.org/html/rfc5280). К счастью, это реализовано за кулисами с помощью функции iOS SecTrustEvaluate, но есть строительные леса, которые нужно построить, и странные вещи, которые нужно понять.

[Небольшая проблема - закончилось место!! Добавлен новый вопрос, включая конец этого шага.]

https://stackoverflow.com/questions/35964632/correctly-use-a-pinned-self-signed-certificate-in-ios-9-2

[Продолжение из другого поста]

Ну это все. Извините за не совсем качественное исполнение, но я хотел сделать это вместе, пока это было еще свежо в моей памяти. Я обновлю пост, если найду ошибки.

Надеюсь, это поможет, и вот последняя ссылка на очень хорошую книгу, которая, среди прочего, научит вас доверять коммерческим центрам сертификации...

https://www.cs.auckland.ac.nz/~pgut001/pubs/book.pdf

person saminpa    schedule 08.03.2016
comment
Привет, чувак, классный пост, однако твой последующий пост был удален. В любом случае вы могли бы предоставить остальное в чате? - person jlmurph; 14.09.2017
comment
К сожалению, кто-то проголосовал против него: я голосую за то, чтобы закрыть этот вопрос как не по теме, потому что это сообщение в блоге, а не вопрос Paulw11 13 март 2016, в 01:14. Может быть, у кого-то есть сила, чтобы реактивировать его, он все еще там? - person saminpa; 16.09.2017
comment
Спасибо, кстати. Я бы с радостью отправил остальные, но на этом форуме нет возможности написать в личку. - person saminpa; 16.09.2017
comment
Я не думаю, что вы можете поместить остальное во второй ответ на вопрос? - person cacsar; 22.03.2018
comment
Добавлен остаток в качестве нового ответа ниже, поскольку исходный вопрос недоступен. ХТН - person saminpa; 24.03.2018
comment
Можете ли вы предоставить ссылку на репозиторий Git для источника. Я пробовал ваше решение, но преуспел. Спасибо @saminpa - person Krishna; 03.08.2018

[Я только что (!) понял, что могу добавить еще один ответ, поскольку ссылка на продолжение была отклонена и закрыта, и есть два запроса на дополнительную информацию, которая не подходит выше. Ответ ниже начинается с вопроса в удаленном посте]

... Часть, которую я до сих пор не понимаю, - это то, почему мне пришлось создать новое доверие и политику для реализации сертификата привязки и закрепления.

Если бы я просто добавил привязку к доверию, полученному от сервера, я бы не смог успешно вернуть указатель на NSURLCredential, полученный от сервера, казалось, что он был изменен и отклонен отправителем (?).

Вопрос, это действительно правильная обработка или можно сконденсироваться? Это становится немного утомительно, но я не хочу принимать что-то только потому, что это «работает». Мое текущее решение показано ниже.

На шаге 4 у вас уже были учетные данные клиента для ответа на этот тип запроса без манипуляций, но сертификат сервера циркулирует как NSData в формате DER, созданный SecCertificateCopyData, и неясно, что должно произойти, когда этот запрос прибудет.

Оказывается, вам нужно реализовать алгоритм из раздела 6 «Стандарта X.509» (https://tools.ietf.org/html/rfc5280). К счастью, это реализовано за кулисами с помощью функции iOS SecTrustEvaluate, но есть строительные леса, которые нужно построить, и странные вещи, которые нужно понять. Сначала код (после подсказки к моему первоисточнику):

SecTrustEvaluate всегда возвращает kSecTrustResultRecoverableTrustFailure с SecPolicyCreateSSL

    - (void)_handleServerTrustChallenge {

    OSStatus status;
    BOOL trusted = false;
    SecTrustResultType trustResult;
    SecTrustRef serverTrust = self.challenge.protectionSpace.serverTrust;
    NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; //candidate credential for return to sender if valid

    if (svDelegate.serverCertificateData) {

        //locally stored information
        SecCertificateRef storedCertificate = SecCertificateCreateWithData(NULL, (CFDataRef)svDelegate.serverCertificateData);
        NSMutableArray *anchorCertArray = [NSMutableArray arrayWithCapacity:1];
        [anchorCertArray addObject:(id)storedCertificate];

        //incoming credentials from server
        NSMutableArray *receivedCertChain = [NSMutableArray array];
        for(int i = 0; i < SecTrustGetCertificateCount(serverTrust); i++)
            [receivedCertChain addObject:(id) SecTrustGetCertificateAtIndex(serverTrust,i))];

        //new custom policy object to use in creating new trust
        //YES indicates extendedKeyUsage is set to serverAuth; effectively ignore server name check by specifying incoming name
        SecPolicyRef newPolicyRef = SecPolicyCreateSSL(YES, (CFStringRef)self.challenge.protectionSpace.host);

        //create and evaluate new trust with pinned certificate
        SecTrustRef newTrustRef = NULL;
        SecTrustCreateWithCertificates((CFArrayRef) receivedCertChain, newPolicyRef, &newTrustRef);
        status = SecTrustSetAnchorCertificates(newTrustRef, (CFArrayRef) anchorCertArray);
        if (status == noErr) status = SecTrustSetAnchorCertificatesOnly(newTrustRef, TRUE);
        if (status == noErr) status = SecTrustEvaluate(newTrustRef, &trustResult);  

        //----- debug -------
        //CFShow(newPolicyRef);
        //NSLog(@"%@", receivedCertChain);     

        CFRelease(newTrustRef);
        CFRelease(newPolicyRef);
        CFRelease(storedCertificate);
    }
    else {  //Server certificate not stored, rely on standard trusted Root CA authorities

        status = SecTrustEvaluate(serverTrust, &trustResult);
    }

    trusted = (status == noErr) && (trustResult == kSecTrustResultUnspecified);

    if (!trusted) credential = nil;
    [self stopWithCredential:credential];
    [self.delegate challengeHandlerDidFinish:self];
}

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

Затем вы выбираете «объект доверия» для оценки. Мне не удалось сделать это, не сделав рабочую копию объекта доверия, который я получил по сети с сервера, потому что он каким-то образом испортил ссылку «NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]», если я использовал ее напрямую. Однако из действительно ужасных документов Apple следует, что это кошерный подход (я, например, рекомендую просмотреть x.509 rfc, если вы хотите что-то понять).

https://stackoverflow.com/questions/7900896/sectrustevaluate-always-returns-ksectrustresultrecoverabletrustfailure-with-secp

[https://developer.apple.com/library/ios/technotes/tn2232/_index.html#//apple_ref/doc/uid/DTS40012884-CH1-SECCUSTOMROOT][2]

Доверию требуется «политика», входящая цепочка сертификатов для оценки и один или несколько «привязочных сертификатов», которые в основном определяют источник в произвольной системе координат — все проверяется после нулевой точки, даже если это не корневой сертификат. .

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

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

Когда вы оцениваете доверие, может показаться странным, что вы принимаете kSecTrustResultUnspecified и не продолжаете, или что-то более позитивное. На самом деле, продолжить означает, что вы следуете переопределениям из пользовательского интерфейса, так что на самом деле это плохо; unspecified просто означает, что в указанной политике нет ничего плохого.

Наконец, вы возвращаете учетные данные из входящего объекта доверия (не нового), и все должно быть золотым...

person saminpa    schedule 24.03.2018
comment
это не работает... сертификат клиента не отправляется. - person Krishna; 14.08.2018
comment
Кришна, извини, но это сообщение двухлетней давности, и я не могу догадаться, что не так с твоим кодом или есть ли проблема из-за обновлений ОС. Тем не менее, я бы использовал отладчик с физическим устройством (не симулятором) и проверил, что ваш обработчик вообще выполняется и что устройство не отклоняет входящий сертификат сервера, потому что он не из общедоступного ЦС (самоподписанный) . Или вы можете опубликовать вопрос со своим кодом, и, возможно, кто-то поможет понять, что не так. - person saminpa; 14.08.2018
comment
Я использую WKWebview... Доверие к серверу работает... но когда я вижу дамп tcp, я вижу, что сертификат клиента не отправляется... Ваш пост очень помог... но я не знаю, где его найти ответ на это.. - person Krishna; 14.08.2018
comment
Боюсь, я ничего не знаю о WKWebview, но если вы можете подтвердить, что у вас есть действительные данные на шаге 4 выше в вашей строке, соответствующей: self.clientCredential = [NSURLCredential credentialWithIdentity:clientIdentity certificates:(NSArray*)certsArray persistence:NSURLCredentialPersistenceNone]; Затем я бы задал вопрос о том, как убедиться, что он добавлен в ответ в вашем сценарии. - person saminpa; 14.08.2018