iOS: предварительно установить SSL-сертификат в связке ключей - программно
Я хочу установить / сохранить сертификат в связке ключей до того, как пользователь заходит на сайт. У меня есть HTTPS-сервер, и мое приложение проверяет подлинность пользователя, прежде чем он перейдет на https://mysite/. Есть ли способ, которым я могу установить / сохранить сертификат через почтовый запрос в цепочке для ключей. ИЛИ Я копирую этот сертификат (файл) в пакет ресурсов, чтобы отметить его как доверенный.
Спасибо
аль
2 ответа
Получив сертификат сервера в формате der, вы можете попробовать следующий код:
+ (void) addCertToKeychain:(NSData*)certInDer
{
OSStatus err = noErr;
SecCertificateRef cert;
cert = SecCertificateCreateWithData(NULL, (CFDataRef) certInDer);
assert(cert != NULL);
CFTypeRef result;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassCertificate, kSecClass,
cert, kSecValueRef,
nil];
err = SecItemAdd((CFDictionaryRef)dict, &result);
assert(err == noErr || err == errSecDuplicateItem);
CFRelease(cert);
}
Он добавит сертификат в изолированную программную среду цепочки для ключей вашего приложения, т.е. никакое другое приложение не будет доверять вашему сертификату.
У вас есть два варианта: добавить сертификат сервера в связку ключей или выполнить проверку вручную. Независимо от вашего подхода, вам нужно будет включить в ваше приложение открытый сертификат X.509 в кодировке DER. В приведенном ниже примере он называется "ios-trust-cert.der") и создайте с ним SecCertificateRef. (Если сертификат вашего сервера является частью цепочки с корневым центром сертификации, вам следует установить корневой центр сертификации, а не сертификат вашего сервера.)
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSData *iosTrustedCertDerData =
[NSData dataWithContentsOfFile:[bundle pathForResource:@"ios-trusted-cert"
ofType:@"der"]];
SecCertificateRef certificate =
SecCertificateCreateWithData(NULL,
(CFDataRef) iosTrustedCertDerData);
Помните, что SecCertificateCreateWithData следует правилу создания владения памятью, поэтому вы должны CFRelease, когда он вам больше не нужен, чтобы избежать утечек памяти.
Затем вы можете добавить свой сертификат в связку ключей вашего приложения. Это подходит, когда вы хотите, чтобы iOS доверял вашему сертификату для каждого нового сокета, который вы создаете.
- (void) useKeychain: (SecCertificateRef) certificate {
OSStatus err =
SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(id) kSecClassCertificate, kSecClass,
certificate, kSecValueRef,
nil],
NULL);
if ((err == noErr) || // success!
(err == errSecDuplicateItem)) { // the cert was already added. Success!
// create your socket normally.
// This is oversimplified. Refer to the CFNetwork Guide for more details.
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)@"localhost",
8443,
&readStream,
&writeStream);
CFReadStreamSetProperty(readStream,
kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelTLSv1);
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
} else {
// handle the error. There is probably something wrong with your cert.
}
}
Если вы хотите проверить сертификат только для сокета, который вы создаете, и для других сокетов в вашем приложении нет, вы можете проверить свое доверие к сертификату вручную. Сначала создайте сокет (при условии, что ваш сервер прослушивает порт 8443 на той же машине, что и ваш клиент) и отключите проверку цепочки сертификатов в настройках ssl:
- (void) verifiesManually: (SecCertificateRef) certificate {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)@"localhost",
8443,
&readStream,
&writeStream);
// Set this kCFStreamPropertySocketSecurityLevel before
// setting kCFStreamPropertySSLSettings.
// Setting kCFStreamPropertySocketSecurityLevel
// appears to override previous settings in kCFStreamPropertySSLSettings
CFReadStreamSetProperty(readStream,
kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelTLSv1);
// this disables certificate chain validation in ssl settings.
NSDictionary *sslSettings =
[NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
nil];
CFReadStreamSetProperty(readStream,
kCFStreamPropertySSLSettings,
sslSettings);
NSInputStream *inputStream = (NSInputStream *)readStream;
NSOutputStream *outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
}
Затем, когда вы получите обратный вызов о том, что ваш сокет готов к записи данных, вы должны проверить доверие к сертификату, включенному в ваш сервер, прежде чем записывать какие-либо данные на сервер или читать их с сервера. Сначала (1) создайте клиентскую политику SSL с именем хоста сервера, к которому вы подключились. Имя хоста включается в сертификат сервера, чтобы подтвердить, что сервер, на который направил вас DNS, является сервером, которому вы доверяете. Далее (2) вы извлекаете действительные сертификаты сервера из сокета. С сервером может быть связано несколько сертификатов, если сертификат сервера является частью цепочки сертификатов. Когда у вас есть действительные сертификаты сервера, вы можете (3) создать объект доверия. Объект доверия представляет локальный контекст для оценки доверия. Он изолирует отдельные оценки доверия, тогда как сертификаты цепочки ключей применяются ко всем доверенным сокетам. После того, как у вас есть объект доверия, вы можете (4) установить сертификаты привязки, которые являются сертификатами, которым вы доверяете. Наконец (5), вы можете оценить объект доверия и выяснить, можно ли доверять серверу.
#pragma mark -
#pragma mark NSStreamDelegate
- (void)stream:(NSStream *)aStream
handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventNone:
break;
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
break;
case NSStreamEventHasSpaceAvailable:
// #1
// NO for client, YES for server. In this example, we are a client
// replace "localhost" with the name of the server to which you are connecting
SecPolicyRef policy = SecPolicyCreateSSL(NO, CFSTR("localhost"));
SecTrustRef trust = NULL;
// #2
CFArrayRef streamCertificates =
[aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates];
// #3
SecTrustCreateWithCertificates(streamCertificates,
policy,
&trust);
// #4
SecTrustSetAnchorCertificates(trust,
(CFArrayRef) [NSArray arrayWithObject:(id) self.certificate]);
// #5
SecTrustResultType trustResultType = kSecTrustResultInvalid;
OSStatus status = SecTrustEvaluate(trust, &trustResultType);
if (status == errSecSuccess) {
// expect trustResultType == kSecTrustResultUnspecified
// until my cert exists in the keychain see technote for more detail.
if (trustResultType == kSecTrustResultUnspecified) {
NSLog(@"We can trust this certificate! TrustResultType: %d", trustResultType);
} else {
NSLog(@"Cannot trust certificate. TrustResultType: %d", trustResultType);
}
} else {
NSLog(@"Creating trust failed: %d", status);
[aStream close];
}
if (trust) {
CFRelease(trust);
}
if (policy) {
CFRelease(policy);
}
break;
case NSStreamEventErrorOccurred:
NSLog(@"unexpected NSStreamEventErrorOccurred: %@", [aStream streamError]);
break;
case NSStreamEventEndEncountered:
break;
default:
break;
}
}