Как реализовать клиентские сертификаты и проверку подлинности сервера для iOS
Недавно я прошел через чрезвычайно трудный процесс создания чего-то, что должно быть очень простым, но, по-видимому, практически невозможно найти ни в одном месте. Я хотел бы попытаться поместить все здесь, чтобы спросить, делаю ли я что-то не так, и, если нет, помочь любому, кто нуждается в этой информации.
Предыстория: продукт / услуга, для которой я пытался обеспечить безопасность, построена вокруг служб WCF на сервере Windows, которые доступны только через пользовательские клиентские приложения на ПК или iPad. Один сервер на клиента, нет доступа к браузеру. Все было уже защищено TLS с помощью аутентификации и авторизации с использованием стандартных механизмов Windows и сертификатов от коммерческого ЦС.
Для дальнейшего ограничения доступа клиент-серверные сертификаты были реализованы для платформы Windows с использованием самозаверяющих сертификатов (коммерческие ЦС не требуются в случаях, когда существует взаимная аутентификация без публичного доступа / доступа через браузер - несмотря на заявления об обратном - и они сложнее управлять).
Заставить все это работать на iPad было ужасно задокументированным кошмаром с удивительным количеством дезинформации или частично правильных рекомендаций. В дальнейшем я попытался ссылаться на лучшие источники, но я прошу прощения, если я случайно пропустил атрибуцию. Пожалуйста, прокомментируйте, если что-то не так / вводит в заблуждение об этом посте.
Спасибо
2 ответа
Основными этапами являются:
- Создать систему для генерации сертификатов (легко, но нетривиально, если это производственная система)
- Перенесите сертификаты на iPad (НЕ встроенный в комплект магазина приложений!)
- Сохраните все полученные учетные данные в связке ключей приложения (где Apple говорит, что они принадлежат)
- Получить сохраненные учетные данные из цепочки для ключей для использования в NSURLConnections
- Фактически аутентифицируйте сертификат сервера и верните учетные данные клиента
Шаг 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/client, сохраняйте корень в блокировке) Вот моя файловая структура:
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
В папке certs\template
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 = info@yourcompany.com
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 = info@yourcompany.com
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, вам будет просто понять, что делать дальше... НЕТ. Кажется, что вся документация относится к хранилищу имен / pwd, и страшное количество плакатов предлагает сохранить сертификат сервера в файловой системе, что нормально, но не имеет никакого смысла, когда у вас есть цепочка для ключей, и Apple говорит, что это для чего. И последнее, но не менее важное: как вы различаете хранимые сертификаты и как их обновлять?
Короче говоря, я решил сделать полное удаление / восстановление после того, как попробовал все виды вещей, которые не работают, чтобы проверить, должно ли это быть обновление или первоначальная загрузка - кроме того, что я хотел сделать в первую очередь, так как это мой цепочка приложений. Все это вещи CF, и я не использую ARC, потому что я отказываюсь переносить все, что мне не нужно. Насколько я могу судить, пока вы выделяете CF, приведение к NS и CFRelease после использования, предупреждений нет.
Это ключевые ссылки:
Перечислите все элементы цепочки для ключей в моем приложении 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 и доверенные сертификаты по умолчанию. Для моего сценария, с помощью закрепления сертификата и взаимной аутентификации между выделенными клиентами и частным сервером, вы можете безопасно отключить эту функцию, добавив словарь в свой список следующим образом:
Затем, на шаге 4, у вас уже были учетные данные клиента для немедленного ответа на этот вызов, когда он срабатывает, но сертификат сервера перемещается в виде NSData в формате DER, как это было создано SecCertificateCopyData, и неясно, что должно произойти, когда появится этот вызов.
Оказывается, что вы должны сделать, это реализовать алгоритм в разделе 6 "стандарта X.509" ( https://tools.ietf.org/html/rfc5280). К счастью, это реализовано за кулисами с помощью функции iOS SecTrustEvaluate, но есть леса для создания и странные вещи для понимания.
[Небольшая проблема - не хватило места!! Добавлен новый вопрос, в том числе конец этого шага.]
https://stackru.com/questions/35964632/correctly-use-a-pinned-self-signed-certificate-in-ios-9-2
[Продолжая с другого поста]
Итак, это все. Извините за не совсем хорошее качество производства, но я хотел дать ему пощечину, пока это было свежо в моей памяти. Я буду обновлять пост, если я найду ошибки.
Надеюсь, что это поможет, и вот последняя ссылка на очень хорошую книгу, которая, помимо прочего, даст вам представление о доверии коммерческих CA.
[Я только (!) Понял, что могу добавить еще один ответ, так как ссылка на продолжение была отклонена и закрыта, и есть два запроса на дополнительную информацию, которая не подходит выше. Ответ ниже начинается с вопроса в удаленном посте]
... Часть, о которой я до сих пор не понимаю, - это то, почему мне пришлось создать новое доверие и политику для реализации привязки сертификата и закрепления.
Если бы я просто добавил привязку к доверию, полученному от сервера, я не смог бы успешно вернуть указатель на 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 видно, что это кошерный подход (например, я рекомендую снимать rfc x.509, если вы хотите понять что-то из этого).
[ https://developer.apple.com/library/ios/technotes/tn2232/_index.html
Доверие требует "политики", цепочки входящих сертификатов для оценки и одного или нескольких "якорных сертификатов", которые в основном определяют начало координат в произвольной системе координат - все проверяется после нулевой точки, даже если это не корневой сертификат,
Таким образом, вы загружаете входящую цепочку и сохраненный сертификат в массивы для предоставления новому доверию и создаете новую политику с SecPolicyCreateSSL - это устанавливает флаг, указывающий, что сертификат должен быть проверен на предмет выдачи для serverAuth и что входящий сервер имя следует игнорировать (чтобы обеспечить некоторую гибкость инфраструктуры).
Затем вы создаете новое доверие, используя новую политику и массив сертификатов для аутентификации. Затем вы устанавливаете привязку и гарантируете, что цепочка будет оцениваться только по вашему сертификату привязки, а не только по какой-либо цепочке ключей iOS.
Когда вы оцениваете доверие, может показаться странным, что вы принимаете kSecTrustResultUnspecified и не продолжаете или что-то более позитивно звучащее. Фактически, продолжить означает, что вы следуете переопределениям из пользовательского интерфейса, так что это на самом деле плохо; просто не указано, что в указанной политике нет ничего плохого.
Наконец, вы возвращаете учетные данные из входящего объекта доверия (не нового), и все должно быть золотым...