Основные шаги:
- Создайте систему для генерации сертификатов (просто, но нетривиально, если это производственная система)
- Перенесите сертификаты на 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/клиент, держите корень в блокировке) Вот моя файловая структура:
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