TCP-сервер Objective-C с поддержкой SSL / TLS

У меня есть простая настройка TCP Server / Client. Эта связь на самом деле прекрасно работает.

Теперь я хотел реализовать шифрование SSL / TLS для сокетных соединений. Я создал сертификат PKCS12, используя доступ к цепочке для ключей. На моем сервере у меня есть следующий код внутри обратного вызова accept:

NSString *certificatePath = [[NSBundle mainBundle] pathForResource:@"TCPServerCertificate" ofType:@"p12"];
NSData *certificateData = [NSData dataWithContentsOfFile:certificatePath];

CFArrayRef keyRef;
OSStatus status = SecPKCS12Import((__bridge CFDataRef)certificateData, (__bridge CFDictionaryRef)@{(__bridge NSString *)kSecImportExportPassphrase: @"1234"}, &keyRef);

if (status != noErr) {
    NSLog(@"PKCS12 import error %i", status);
    return;
}

CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyRef, 0);
SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);

SecCertificateRef certificate;
status = SecIdentityCopyCertificate(identityRef, &certificate);

if (status != noErr) {
    NSLog(@"sec identity copy failed: %i", status);
    return;
}

NSArray *certificates = [NSArray arrayWithObjects:(__bridge id)identityRef, (__bridge id)certificate, nil];

NSDictionary *settings = @{(NSString *)kCFStreamPropertyShouldCloseNativeSocket:    [NSNumber numberWithBool:YES],
                           (NSString *)kCFStreamSSLValidatesCertificateChain:       [NSNumber numberWithBool:YES],
                           (NSString *)kCFStreamSSLAllowsExpiredCertificates:       [NSNumber numberWithBool:NO],
                           (NSString *)kCFStreamSSLAllowsExpiredRoots:              [NSNumber numberWithBool:NO],
                           (NSString *)kCFStreamSSLAllowsAnyRoot:                   [NSNumber numberWithBool:YES],
                           (NSString *)kCFStreamSSLCertificates:                    certificates,
                           (NSString *)kCFStreamSSLIsServer:                        [NSNumber numberWithBool:YES],
                           (NSString *)kCFStreamSSLLevel:                           (NSString *)kCFStreamSocketSecurityLevelTLSv1};

CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);

Тогда я создаю NSStream экземпляры потоков и обрабатывать их в другом классе.

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

2013-10-25 13:27:08.584 TCPServer[6435:303] CFNetwork SSLHandshake failed (-9800)
2013-10-25 13:27:08.584 TCPServer[6435:303] NSStreamEventOpenCompleted
2013-10-25 13:27:08.585 TCPServer[6435:303] NSStreamEventErrorOccurred

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

Существуют ли хорошие учебные пособия по TCP / TCP или другие материалы, касающиеся как клиента, так и сервера?

1 ответ

Решение

Есть некоторые проблемы с этим кодом: вы не можете установить kCFStreamPropertyShouldCloseNativeSocket с kCFStreamPropertySSLSettings, и вы не должны смешивать код сервера и клиента.

Для сервера вы должны просто установить сертификат

CFReadStreamSetProperty(read, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(write, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
//kCFStreamPropertySocketSecurityLevel
//Note: If you set this key, you must do so before setting any other SSL options, such as kCFStreamPropertySSLSettings.
CFReadStreamSetProperty(read, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL);
CFWriteStreamSetProperty(write, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL);
//Creating server dictionnary

//kCFStreamSSLIsServer
//If the value of this key is kCFBooleanTrue, the kCFStreamSSLCertificates key must contain a valid value

//kCFStreamSSLCertificates
//Security property key whose value is a CFArray of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef.
//For more information, see SSLSetCertificate() in Security/SecureTransport.h.

NSDictionary *settings = @{(id)kCFStreamSSLCertificates:                    certificates,
                           (id)kCFStreamSSLIsServer:                        @YES};


//Apply settings
CFReadStreamSetProperty(read, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings));
CFWriteStreamSetProperty(write, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings));

Для клиента, если вы хотите переопределить цепочку проверки ( см. Документ Apple о переопределении цепочки проверки), вы должны сделать следующее:

CFReadStreamSetProperty(read, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(write, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
//kCFStreamPropertySocketSecurityLevel
//Note: If you set this key, you must do so before setting any other SSL options, such as kCFStreamPropertySSLSettings.

CFReadStreamSetProperty(read, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL);
CFWriteStreamSetProperty(write, kCFStreamPropertySocketSecurityLevel, kCFStreamSocketSecurityLevelNegotiatedSSL);

//DO NOT USE kCFStreamPropertySSLContext as it overrides the following configuration
//create dictionnary for kCFStreamPropertySSLSettings

//keys for dictionnary we want to change:kCFStreamSSLAllowsExpiredCertificates;kCFStreamSSLAllowsExpiredRoots;kCFStreamSSLAllowsAnyRoot;
//kCFStreamSSLValidatesCertificateChain => no need to worry about the root
//kCFStreamSSLPeerName kCFNull prevents name verification


settings = @{(id)kCFStreamSSLValidatesCertificateChain: @NO,//The delegate will verify this
             (id)kCFStreamSSLPeerName: (id)kCFNull};//prevents name verification if server is not fixed (eg. IP)

//Apply settings
CFReadStreamSetProperty(read, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings));
CFWriteStreamSetProperty(write, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)(settings));

Я только что создал пакет, который упрощает TLS/TCP с новыми ограничениями iOS 13. Надеюсь, это кому-то поможет! Не стесняйтесь вносить свой вклад:

https://github.com/eamonwhiter73/IOSObjCWebSockets

Другие вопросы по тегам