具备SSL/TLS支持的Objective-C TCP服务器

3

我有一个简单的TCP服务器/客户端设置。这个连接实际上非常好用。

现在我想为套接字连接实现SSL / TLS加密。我使用钥匙串访问创建了一个PKCS12证书。在我的服务器中,我在接受回调函数内部拥有以下代码:

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 SSL / TLS教程或其他材料,涵盖了客户端和服务器端?

你在连接的两端使用相同的代码吗?如果是这样,那么看起来你在连接的两端都将 kCFStreamSSLIsServer 属性设置为 YES - Rick Morgan
我现在不在办公室。但这可能是正确的。那么我应该只删除 kCFStreamSSLIsServer 吗? - Julian F. Weinert
我认为你应该保留这行代码 - 在作为服务器的连接端,将其保持不变。但在客户端,您需要在字典中将此值设置为“NO”。 - Rick Morgan
抱歉,我现在不确定。我做过几个TCP测试和教育项目...但我认为当你在这方面挣扎时,你应该尝试一下。 - Julian F. Weinert
2个回答

1
这段代码存在一些问题: 不能在kCFStreamPropertySSLSettings中设置kCFStreamPropertyShouldCloseNativeSocket,而且不应该混合使用服务器和客户端代码。
对于服务器,你只需要设置证书。
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));

如果您想覆盖验证链(请参阅苹果文档有关覆盖链验证的内容),则应执行以下操作:

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));

你能解释一下为什么我需要设置 kCFStreamPropertyShouldCloseNativeSocket 吗? - Julian F. Weinert
kCFStreamPropertyShouldCloseNativeSocket会在对象生命周期结束时关闭连接。否则,您必须手动关闭本机套接字。 - impact27
感谢您当时的帮助。尽管用于防止链验证的属性已被弃用/替换,但您还是让我能够运行起来。现在7年后,让我们再问一个问题:D 我想强制执行TLS,但似乎会退回到SSL。 kCFStreamSocketSecurityLevelTLSv1 导致SSL协议错误...您有什么快速提示吗? - Julian F. Weinert

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接